Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions cip/2.testable/CIP2017-04-24-UNWIND.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
= CIP2017-04-24 UNWIND
:numbered:
:toc:
:toc-placement: macro
:source-highlighter: codemirror

*Author:* Mats Rydberg <[email protected]>

[abstract]
.Abstract
--
This CIP details the `UNWIND` clause, which can be used to turn a list into a set of records.
The modified version `OPTIONAL UNWIND` is also contained here.
--

toc::[]

== Proposal

In Cypher, the `collect()` aggregator collapses the records of a query into a list (or groups of lists).
The `UNWIND` clause provides the inverse of these semantics, turning an argument list into a set of records, one for each element, and producing as output the product of the incoming records and the list records.

Additionally, a modified version of `UNWIND` may be specified by prepending the keyword `OPTIONAL`, which provides a different semantics for the special cases of empty list and `null` arguments.
This mirrors the way `OPTIONAL` modifies semantics for the `MATCH` clause, and bears similarities to the SQL left outer join.

This CIP aims to resolve https://github.com/opencypher/openCypher/issues/234[CIR-2017-234].

=== Syntax

The syntax for `UNWIND` follows the simple pattern below.

.UNWIND clause:
[source, ebnf]
----
unwind = ["OPTIONAL"], "UNWIND", expression, "AS", variable ;
----

=== Semantics

The argument to the `UNWIND` clause is an expression that is assumed to be a list.
In case the argument is a non-null non-list, the value of the expression will be considered as comprising a single-element list.

The cardinality of the query after the `UNWIND` will be the product of the incoming cardinality and the size of the argument list.
This means that _single-element lists_ will just be unwrapped into a new variable, and _empty lists_ will cancel out the cardinality of the surrounding context, effectively halting execution of that query branch.
Arguments that are `null` are interpreted as empty lists, and follow the same semantics.

In `OPTIONAL UNWIND`, empty lists and `null` arguments will yield a single output record with a `null` value.

No fields are hidden by the `UNWIND` clause, which is to say that it functions similarly to `WITH *`.
In fact, in case the argument to `UNWIND` is a single-element list, or a non-null non-list, `UNWIND` will function purely as an aliasing operation.

=== Examples

.Unwinding a literal list:
[source, cypher]
----
WITH [1, 2, 3] AS list
UNWIND list AS number
RETURN number
----

The result of executing the above query will be three records with one field `number` containing the values `1`, `2`, and `3`, respectively.

.Unwinding a collected list:
[source, cypher]
----
MATCH (:Person {name: $parent})<-[:CHILD_OF]-(child:Person)
WITH $parent AS parent, collect(child.age) AS childAges
UNWIND [age IN childAges WHERE age < $param] AS youngChild
RETURN parent, youngChild
----

.Unwinding a non-list:
[source, cypher]
----
WITH 1 AS one, 2 AS two
UNWIND one AS element
RETURN one, two, element
----

The above query will return a single record with the field/value pairs `one: 1`, `two: 2`, `element: 1`.

.Unwinding an empty list:
[source, cypher]
----
WITH [[], [1, 2, 3]] AS lists
UNWIND lists AS list
UNWIND list AS element // will 'skip' the empty list
RETURN element
----

The above query will return three records with the values `1`, `2`, and `3` for the field `element`.

.Unwinding null:
[source, cypher]
----
UNWIND [null, 1, [1]] AS list
UNWIND list AS element // will 'skip' the null value
RETURN element
----

The above query will return two records with the value `1` for the field `element`.

==== OPTIONAL UNWIND

.Optionally unwinding a literal list:
[source, cypher]
----
WITH [1, 2, 3] AS list
OPTIONAL UNWIND list AS number
RETURN number
----

The result is the same as with standard `UNWIND` (see above).

.Optionally unwinding an empty list:
[source, cypher]
----
WITH [[], [1, 2, 3]] AS lists
UNWIND lists AS list
OPTIONAL UNWIND list AS element
RETURN element
----

The above query will return four records with the values `null`, `1`, `2`, and `3` for the field `element`.

.Optionally unwinding null:
[source, cypher]
----
UNWIND [null, 1, [1]] AS list
OPTIONAL UNWIND list AS element
RETURN element
----

The above query will return three records with the values `1`, `1`, and `null` for the field `element`.
Loading