You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
query_graph: UNWIND is parsed but silently ignored (README says supported / should error) #1192
query_graph accepts UNWIND without error but never executes it — the clause is parsed and then ignored by the executor, so the rest of the query runs as if the UNWIND were absent. This contradicts the README, which both lists UNWIND as supported and states that unsupported syntax "fails with a clear unsupported … error." Instead the user gets a silently wrong result.
Reproduction (v0.9.0)
query_graph:
UNWIND [1,2,3] AS x MATCH (f:Function) WHERE f.name = 'main' RETURN f.name
Expected (per README): either 3× row multiplication, or a clear "unsupported" error.
Actual: the UNWIND is ignored; the MATCH runs once and returns its rows with no error and no warning.
Location
Parser accepts UNWIND (src/cypher/cypher.c:1832-1866) but the executor loop (cypher.c:4635-4694) has no UNWIND handling, so x is never bound and no row expansion occurs.
Suggested fix
Either implement UNWIND (bind the list elements and cross-product with subsequent MATCH/RETURN), or — consistent with the documented contract — reject it with the same unsupported … error path used for other unimplemented clauses so it fails loudly instead of silently.
Related
A sibling correctness gap: comparisons against unbound OPTIONAL MATCH variables evaluate TRUE (~cypher.c:2611-2612), which is also a silent-wrong-answer path. Happy to file separately if preferred.
Thank you for this — it is an unusually well-researched report, and both findings reproduce on current main (97ce23f).
UNWIND is confirmed. The clause parses into q->unwind_expr / q->unwind_alias (src/cypher/cypher.c:1858-1865), and those two fields are thereafter only ever freed (cypher.c:2109-2110) — there is no read of either anywhere in the executor. So the parse succeeds, the alias never binds, and no row expansion happens: exactly the silent-wrong-answer path you describe.
The documented contract is also exactly as you quote it — README.md:608 lists UNWIND among the supported clauses, while README.md:614 promises that anything outside the subset "fails with a clear unsupported … error rather than returning empty results." UNWIND currently falls into the gap between those two sentences, which is the worst of both options.
The sibling finding is confirmed as well.cypher.c:2612-2614 reads if (!actual) { return true; } — an unresolvable condition value makes the predicate pass unconditionally, so a WHERE against an unbound OPTIONAL MATCH variable succeeds instead of behaving like a comparison against null. Please do file that separately; it is a distinct root cause with a different blast radius (it affects every OPTIONAL MATCH query, not just UNWIND), and it deserves its own tracking issue.
Which way the UNWIND gap gets closed — implementing the row expansion, or rejecting the clause loudly so it matches the documented contract — is a deliberate design call rather than a triage one, so I am leaving that open here rather than pre-committing. Keeping it at priority/high either way: a silently wrong result is worse than a hard error.
Thanks again for tracing this to the exact lines and for flagging the sibling case rather than bundling it.
Thank you for the precise UNWIND report. The silent acceptance versus actual row expansion remains the correctness issue; I have no new verified outcome or implementation decision to add.
Activity
DeusData commentedon Jul 25, 2026
Thank you for this — it is an unusually well-researched report, and both findings reproduce on current
main(97ce23f).UNWINDis confirmed. The clause parses intoq->unwind_expr/q->unwind_alias(src/cypher/cypher.c:1858-1865), and those two fields are thereafter only ever freed (cypher.c:2109-2110) — there is no read of either anywhere in the executor. So the parse succeeds, the alias never binds, and no row expansion happens: exactly the silent-wrong-answer path you describe.The documented contract is also exactly as you quote it —
README.md:608listsUNWINDamong the supported clauses, whileREADME.md:614promises that anything outside the subset "fails with a clearunsupported …error rather than returning empty results."UNWINDcurrently falls into the gap between those two sentences, which is the worst of both options.The sibling finding is confirmed as well.
cypher.c:2612-2614readsif (!actual) { return true; }— an unresolvable condition value makes the predicate pass unconditionally, so aWHEREagainst an unboundOPTIONAL MATCHvariable succeeds instead of behaving like a comparison against null. Please do file that separately; it is a distinct root cause with a different blast radius (it affects everyOPTIONAL MATCHquery, not justUNWIND), and it deserves its own tracking issue.Which way the
UNWINDgap gets closed — implementing the row expansion, or rejecting the clause loudly so it matches the documented contract — is a deliberate design call rather than a triage one, so I am leaving that open here rather than pre-committing. Keeping it atpriority/higheither way: a silently wrong result is worse than a hard error.Thanks again for tracing this to the exact lines and for flagging the sibling case rather than bundling it.
DeusData commentedon Oct 19, 2026
Thank you for the precise UNWIND report. The silent acceptance versus actual row expansion remains the correctness issue; I have no new verified outcome or implementation decision to add.