Skip to content

cypher parser: NOT binds tighter than comparison — NOT n.age = 30 parses as (NOT n.age) = 30 #939

Description

@ruvnet

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions