Это сводит меня с ума, особенно потому, что ломает мою ментальную модель работы Neo4j.
1. Ошибка: размотка (5)
С инструкция :
Попытка использовать UNWIND в выражении, которое не возвращает список, например UNWIND 5, приведет к ошибке.
Действительно:
WITH 5 AS x
UNWIND x AS y
RETURN y
дает:
Neo.ClientError.Statement.SyntaxError: Type mismatch: expected List<T> but was Integer (line 2, column 8 (offset: 20))
"UNWIND x AS y "
^
2. Работы: раскрутить ([1, [2, 3]])
Так же, как подготовка к следующему шагу:
WITH [1, [2,3]] AS x
UNWIND x AS y
RETURN y
Плюет:
╒═════╕
│"y" │
╞═════╡
│1 │
├─────┤
│[2,3]│
└─────┘
Обратите внимание, что первая строка 1
- не список.
3. Работы: раскручивать (раскручивать ([1, [2, 3]])) *
Surprise
WITH [1, [2,3]] AS x
UNWIND x AS y
UNWIND y AS z
RETURN z
Выходы:
╒═══╕
│"z"│
╞═══╡
│1 │
├───┤
│2 │
├───┤
│3 │
└───┘
Так что Neo в порядке с 1
во вложенном раскручивании.
4. Ошибка: размотка (размотка ([1, 2]))
WITH [1, 2] AS x
UNWIND x AS y
UNWIND y AS z
RETURN z
Ошибка:
Neo.ClientError.Statement.SyntaxError: Type mismatch: expected List<T> but was Integer (line 3, column 8 (offset: 40))
"UNWIND y AS z "
^
Как это возможно?
Я просто не могу понять, как в ленивом контексте разматывание может выборочно завершиться неудачей, если элемент не является списком, но только до тех пор, пока ни одна из (повторяющихся) строк не включает список.
Другими словами, почему 1
в случае 3 в порядке, но не в случае 4?
Для тех, кто заинтересован, вот код для размотки трубы . Там нет ничего, чтобы предположить, как происходит волшебство.
case class UnwindPipe(source: Pipe, collection: Expression, variable: String)
(val id: Id = Id.INVALID_ID)
extends PipeWithSource(source) with ListSupport {
collection.registerOwningPipe(this)
protected def internalCreateResults(input: Iterator[ExecutionContext], state: QueryState): Iterator[ExecutionContext] = {
if (input.hasNext) new UnwindIterator(input, state) else Iterator.empty
}
private class UnwindIterator(input: Iterator[ExecutionContext], state: QueryState) extends Iterator[ExecutionContext] {
private var context: ExecutionContext = _
private var unwindIterator: Iterator[AnyValue] = _
private var nextItem: ExecutionContext = _
prefetch()
override def hasNext: Boolean = nextItem != null
override def next(): ExecutionContext = {
if (hasNext) {
val ret = nextItem
prefetch()
ret
} else Iterator.empty.next()
}
@tailrec
private def prefetch() {
nextItem = null
if (unwindIterator != null && unwindIterator.hasNext) {
nextItem = executionContextFactory.copyWith(context, variable, unwindIterator.next())
} else {
if (input.hasNext) {
context = input.next()
unwindIterator = makeTraversable(collection(context, state)).iterator.asScala
prefetch()
}
}
}
}
}