Skip to content

fix(cypher): stop the early WHERE pruning rows it cannot judge yet - #2253

Open
DeusData wants to merge 1 commit into
mainfrom
distill/1245-cypher-partial-where
Open

DeusData wants to merge 1 commit into
mainfrom
distill/1245-cypher-partial-where

Conversation

@DeusData

Copy link
Copy Markdown
Owner

Distilled from #1245 by @ahundt (upstream b835b5e), carried with Co-authored-by. Re-derived on main's cypher.c rather than cherry-picked — the file has moved ~9k lines since the branch point. #1245 stays open until its distills land.

The bug

WHERE runs twice: early on seed rows (only the first alias bound) to prune, and late (all aliases bound) to decide. Both passes used the same two-valued evaluator, which answers true for a comparison on an alias it cannot see — the permissive default the late pass relies on. NOT and XOR invert whatever they are given, so:

MATCH (a)-[:CALLS]->(b) WHERE NOT b.name CONTAINS "x"

evaluated NOT(true) on every seed and pruned every row. Zero results, no error, for a correct query.

The fix

The early pass now uses a three-valued evaluator: unbound alias → UNKNOWN; NOT of UNKNOWN stays UNKNOWN; AND/OR short-circuit only on a definite operand; XOR with any UNKNOWN is UNKNOWN. Only a definite FALSE prunes. The late pass is untouched, so anything that was correct before is unchanged.

Three deliberate differences from upstream: one shared leaf rule for the tree path and main's legacy flat AND/OR path; a c->variable == NULL guard (on main a literal-only condition like coalesce(0, 1) >= 2 carries no variable — upstream would have passed NULL to strcmp); the flat path keeps main's empty-list-is-true rule.

RED → GREEN

Before the fix, all four new tests fail with row_count == 0, expected 1:
cypher_exec_where_not_on_relationship_target, cypher_exec_where_mixed_alias_and, _or, _xor.
After: cypher 208 passed; cypher + mcp 526 passed, 4 skipped (the Windows-only skips).

eval_expr_partial recurses and is added to CBM_RECURSION_WHITELIST; scripts/check-nolint-whitelist.sh passes.

Formatting note: the whitelist macro was repacked by Homebrew clang-format 22 after the insertion lengthened a line. AlignEscapedNewlines and column packing are version-stable, but CI's clang-format 20 is the judge — if lint flags it, that is the only place to look.

Distilled from #1245 by Andrew Hundt (upstream b835b5e), re-derived on
main's cypher.c rather than cherry-picked -- the file has moved ~9k lines
since the branch point.

The WHERE clause runs twice: once early, on seed rows where only the
first alias is bound, to prune; and once late, when every alias is bound,
to decide. The early pass used the same two-valued evaluator as the late
one. That evaluator answers "true" for a comparison on an alias it cannot
see -- the permissive default that makes the late pass work -- but NOT and
XOR invert whatever they are given, so

    MATCH (a)-[:CALLS]->(b) WHERE NOT b.name CONTAINS "x"

evaluated NOT(true) on every seed and pruned every row. Zero results, no
error, for a query that is correct.

The early pass now uses a three-valued evaluator. A condition on an alias
that is not yet bound is UNKNOWN; NOT of UNKNOWN stays UNKNOWN; AND and OR
short-circuit only on a definite operand; XOR with any UNKNOWN operand is
UNKNOWN. Only a definite FALSE prunes. The late pass is unchanged and
still gives the final two-valued answer, so nothing that was correct
before changes.

Three deliberate differences from the upstream diff:

  - the unbound-alias rule lives in one leaf, eval_condition_partial, so
    the expression-tree path and main's legacy flat AND/OR path cannot
    drift apart;
  - that leaf guards c->variable == NULL. On main a condition whose left
    side is a function of literals, coalesce(0, 1) >= 2, carries no
    variable at all; the upstream version would have passed NULL to
    strcmp;
  - the flat path keeps main's rule that an empty condition list is true.

RED before the fix, all four with row_count == 0, expected 1:
cypher_exec_where_not_on_relationship_target,
cypher_exec_where_mixed_alias_and, ..._or, ..._xor. GREEN after: cypher
208 passed; cypher + mcp 526 passed, 4 skipped (the Windows-only skips).

eval_expr_partial recurses and is added to CBM_RECURSION_WHITELIST.

Co-authored-by: Andrew Hundt <ATHundt@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant