Defect
ruvector-graph's Cypher parser gives NOT higher precedence than the comparison operators. In Cypher, NOT binds looser than comparison, so NOT n.age = 30 must parse as NOT (n.age = 30).
Verified
Dumping MatchClause.where_clause for MATCH (n) WHERE NOT n.age = 30 RETURN n on c6bb23c84:
BinaryOp {
left: UnaryOp { op: Not, operand: Property { object: Variable("n"), property: "age" } },
op: Equal,
right: Integer(30),
}
The NOT has captured only n.age, and the whole thing is an Equal comparing a boolean against 30. Correct would be:
UnaryOp { op: Not, operand: BinaryOp { left: Property{..}, op: Equal, right: Integer(30) } }
Consequence
Any executor evaluating this tree faithfully returns the wrong rows. (NOT n.age) evaluates to a boolean, comparing it to 30 is false for every node, so WHERE NOT <comparison> silently matches nothing. It does not error — it returns an empty result, which reads as "no matching rows" rather than "your query was misparsed".
<> is unaffected (n.age <> 30 parses correctly), so the workaround is to use <> and De Morgan the rest by hand.
Scope
This is in the shared parser (crates/ruvector-graph/src/cypher/parser.rs), so it affects every consumer of parse_cypher, not just the Node binding. Fixing it means moving NOT below the comparison level in the precedence climb, and checking the existing parser tests don't encode the current behaviour.
Found while implementing #879's WHERE evaluation (PR #938). Deliberately left out of that PR — a precedence change in a shared parser has a wider blast radius than the binding fix and deserves its own review. PR #938 documents the gap next to the test it had to drop.
Related
Defect
ruvector-graph's Cypher parser givesNOThigher precedence than the comparison operators. In Cypher,NOTbinds looser than comparison, soNOT n.age = 30must parse asNOT (n.age = 30).Verified
Dumping
MatchClause.where_clauseforMATCH (n) WHERE NOT n.age = 30 RETURN nonc6bb23c84:The
NOThas captured onlyn.age, and the whole thing is anEqualcomparing a boolean against30. Correct would be:Consequence
Any executor evaluating this tree faithfully returns the wrong rows.
(NOT n.age)evaluates to a boolean, comparing it to30is false for every node, soWHERE NOT <comparison>silently matches nothing. It does not error — it returns an empty result, which reads as "no matching rows" rather than "your query was misparsed".<>is unaffected (n.age <> 30parses correctly), so the workaround is to use<>and De Morgan the rest by hand.Scope
This is in the shared parser (
crates/ruvector-graph/src/cypher/parser.rs), so it affects every consumer ofparse_cypher, not just the Node binding. Fixing it means movingNOTbelow the comparison level in the precedence climb, and checking the existing parser tests don't encode the current behaviour.Found while implementing #879's
WHEREevaluation (PR #938). Deliberately left out of that PR — a precedence change in a shared parser has a wider blast radius than the binding fix and deserves its own review. PR #938 documents the gap next to the test it had to drop.Related
query()ignoredWHEREentirely, so this was unreachable before nowWHEREevaluation;cypher_exec.rsnotes this precedence gap