Skip to content

feat(gfql/cypher): EXISTS { pattern } subqueries — declarative prune-isolated, native on 4 engines#1679

Merged
lmeyerov merged 12 commits into
masterfrom
feat/gfql-cypher-exists
Jul 5, 2026
Merged

feat(gfql/cypher): EXISTS { pattern } subqueries — declarative prune-isolated, native on 4 engines#1679
lmeyerov merged 12 commits into
masterfrom
feat/gfql-cypher-exists

Conversation

@lmeyerov

@lmeyerov lmeyerov commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Implements EXISTS { <pattern> } pattern-existence subqueries (openCypher-standard) in WHERE position, native on all four engines (pandas/cuDF/polars/polars-gpu) — the declarative prune-isolated building block for the streamgl-viz filter pipeline (frozen semantics: the panel-algebra doc, viz-filter L1). Stacked on #1677.

What's added

  • Parser: EXISTS { <pattern> } / NOT EXISTS { ... } in WHERE. The body parses into the SAME BooleanExpr(op="pattern") leaf as a bare pattern predicate — zero new lowering machinery; NOT composes through the existing lift into anti_semi_apply. Inline block comments and property maps ({w: 1}) inside the pattern work. RETURN/WITH-position EXISTS and the not((a)--(b)) spellings keep their clean fail-fast.
  • Existential locals: aliases introduced inside the braces are allowed (EXISTS { (n)--(m) } with m unbound outside) — the correlated bindings project them away. Bare pattern predicates keep the conservative no-new-aliases guard.
  • Drop-self flavor: the one supported inner WHERE form is endpoint inequality — EXISTS { (n)--(m) WHERE m <> n } ("has a neighbor other than itself"). Carried as a safelisted neq= param on the semi-apply ops: pandas/cuDF filter the correlated bindings table; polars excludes self-loop edges (exactly the m <> n witness for a single-edge pattern).
  • Polars goes native (was honest-NIE): semi_apply_mark / anti_semi_apply (correlated key sets via the polars chain executor's named-flag columns — the backward prune makes the flag exactly "a full pattern match exists through this node"; order-preserving is_in instead of joins, null keys matching pandas merge semantics) and rows(binding_ops=...) for the single-entity row table (mirrors the pandas alias-lookup-frame layout). v1 declines honestly: multi-alias correlation, non-single-hop shapes, query= nodes.

Prune-isolated, declaratively (the point)

MATCH (n) WHERE EXISTS { (n)--() } RETURN n              // keep-self flavor
MATCH (n) WHERE EXISTS { (n)--(m) WHERE m <> n } RETURN n // viz drop-self flavor

Both flavors are oracle-pinned in the conformance matrix on a discriminator graph (an isolated node + a self-loop-only node), 4-engine parity-or-NIE.

Tests / verification (dgx GB10, --gpus all)

Full polars lane + conformance matrix + GPU suite + row-pipeline ops = 2082 passed, 0 failed (final tip); cypher lowering suite 1193+ passed (EXISTS marker-shape parity with bare patterns, NOT→anti, neq threading, boundary declines). End-to-end sanity: NOT EXISTS {(n)--()} returns exactly the isolated node on pandas == cuDF == polars. Conformance-ledger waiver text updated to native status (join_apply remains the honest-NIE waiver). ruff + mypy clean.

Honest boundaries (clear errors, no wrong answers)

EXISTS in RETURN/WITH projections; general inner WHERE clauses; multi-pattern bodies; full MATCH..RETURN subquery bodies; multi-alias correlation on polars (pandas/cuDF handle it via the pre-existing bindings machinery).

Review hardening (3-wave converged adversarial review, 2026-07-05)

Findings-mode review (artifacts: plans/viz-filter-pipeline/waves/pr1679/) found and fixed, dgx-verified: GRAPH { } + EXISTS was silently dropping the filter (all engines — now fails fast, anchored to the grammar's constructor-at-start rule with zero false positives proven); the working graph-state prune patterns are pinned 4-engine per the graph-shape acceptance requirement (GRAPH { MATCH (a)-[e]-(b) } keep-self with ALL edges; + WHERE a.id <> b.id drop-self); EXISTS-body keyword scans run on masked text (property strings like 'WHERE it hurts' no longer falsely decline); neq validates as an exact string pair (was a late IndexError / silent first-pair filter); self-alias EXISTS { (n)--(n) } declines instead of a non-NIE crash; null-key and alias-collision corners decline honestly; the pandas name-flag column leak is mirrored exactly; native-must/multi-alias-NIE/decline-shape pins added (changed-line coverage gate green). TCK: the openCypher existential-subquery scenario expr-existentialsubquery1-3 is formally promoted to supported in the tck-gfql corpus (mirror branch feat/gfql-cypher-exists); 1-1 tracked as a whole-entity-rendering wrong-rows debt. Waves 2–3 converged at zero BLOCKER/IMPORTANT. Known follow-up (pre-existing at base): bare pattern predicates inside GRAPH { } bodies also silently drop (row_pre_filters unwired in the graph compiler) — L3 item.

Post-convergence tip movements (structure/CI only, re-verified)

pattern_apply.py module split (module-per-family, per review steer): the polars semi-apply/pattern-existence lowerings move out of row_pipeline.py into graphistry/compute/gfql/lazy/engine/polars/pattern_apply.py — dispatch imports updated, no behavior change (dgx 1703-pass; post-rebase import-shape greps). The polars coverage-audit baseline gains the required pattern_apply.py entry (floor 67.0; dgx-measured 75.36% on the CI-verbatim CPU lane, identical on this and the L2 tree) — the audit fails un-baselined target files, which was the only CI red. CI green at the final tip.

🤖 Generated with Claude Code

inside ``GRAPH { }`` pipelines. For prune-isolated in GRAPH STATE (nodes AND
edges back), use edge patterns instead: ``GRAPH { MATCH (a)-[e]-(b) }`` keeps
every edge-touching node with ALL its edges (self-loops included); add
``WHERE a.id <> b.id`` for the drop-self-loop variant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is probably fuzzing & tck repo work we should do across the pr's

out.append(WherePatternPredicate(
pattern=cur.pattern, span=cur.span, negated=False,
pattern_origin=getattr(cur, "pattern_origin", "bare"),
pattern_neq=getattr(cur, "pattern_neq", None)))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid dynamic typing patterns

pattern_preds.append(WherePatternPredicate(
pattern=leaf.pattern, span=leaf.span, negated=False,
pattern_origin=getattr(leaf, "pattern_origin", "bare"),
pattern_neq=getattr(leaf, "pattern_neq", None)))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid dynami typin gpatterns here too

Comment thread graphistry/compute/gfql/lazy/engine/polars/pattern_apply.py Outdated
@lmeyerov lmeyerov force-pushed the dev/gfql-cypher-std-conformance branch from 3e2c0ea to ccd440e Compare July 5, 2026 22:11
lmeyerov added a commit that referenced this pull request Jul 5, 2026
#1682's test_every_grammar_rule_is_exercised_by_the_corpus requires every
grammar rule to appear in DIFFERENTIAL_CORPUS. #1679's exists_subquery_atom
rule needs coverage — add representative EXISTS / NOT EXISTS queries (single
Earley derivation, so the semantic-ambiguity invariant still holds).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmeyerov lmeyerov force-pushed the feat/gfql-cypher-exists branch from c0a8c1f to 08c1e01 Compare July 5, 2026 22:11
Comment thread graphistry/compute/gfql/lazy/engine/polars/pattern_apply.py Outdated
lmeyerov and others added 11 commits July 5, 2026 15:27
… pattern-existence)

EXISTS_SUBQUERY token + ?primary rule + transformer emitting the SAME
BooleanExpr(op='pattern') leaf as bare pattern predicates — the entire existing
lift/marker/semi-apply lowering applies unchanged (NOT EXISTS -> anti_semi_apply).
Pre-scan: not((..)) spellings + RETURN/WITH-position exists{} keep the clean
fail-fast; WHERE position now parses. v1 declines (clear E108): inner WHERE,
multi-pattern bodies, MATCH..RETURN bodies. dgx: lowering 1192 pass; pandas+cudf
end-to-end (NOT EXISTS{(n)--()} = isolated nodes); polars honest-NIE (native in
the next commit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ing_ops) — EXISTS runs on all 4 engines

Key sets via chain_polars named-flag columns (the backward prune makes the flag
exactly 'a full pattern match exists through this node'); order-preserving is_in
(no join reorders; null keys match pandas merge-no-match semantics); single-Node
rows(binding_ops) mirrors the pandas alias-lookup-frame layout. v1 bounds (honest
NIE): multi-alias correlation, non-[Node,Edge,Node] shapes, node query=. Matrix
EXISTS cases (4-engine parity-or-NIE) + ledger waiver text updated to native
status. dgx: 1991 passed full lane; EXISTS/NOT-EXISTS parity pandas==cudf==polars.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…} on all 4 engines

Existential locals allowed for EXISTS-origin pattern leaves (bindings project them
away; bare predicates keep the conservative guard); the ONE supported inner-WHERE
form = endpoint inequality, carried as neq= on semi_apply_mark/anti_semi_apply
(safelisted): pandas/cuDF filter the bindings table; polars pre-drops self-loop
edges (a non-self edge IS the m<>n witness). Matrix: both panel-algebra §3
prune-isolated flavors oracle-pinned on a self-loop-only discriminator node,
4-engine parity-or-NIE. dgx: 1992 passed full lane + lowering 1193.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…steer)

GRAPH{}+EXISTS was SILENTLY DROPPING the filter (dgx-repro'd all-nodes wrong
answer) -> fail-fast pointing at the working pattern; THE viz graph-shape
patterns pinned 4-engine: GRAPH { MATCH (a)-[e]-(b) } = keep-self prune-isolated
w/ FULL graph, + WHERE a.id <> b.id = drop-self (pre-existing cudf same-path
TypeError documented+tolerated). Wave-1: masked keyword scans in EXISTS bodies
(property strings no longer falsely decline); safelist neq = exactly-2 pair;
alias==node_id + chain-validation + null-key declines; pandas name-flag-leak
mirrored (alias.alias col); native-must + multi-alias-NIE + self-alias pins;
reject-pin tests flipped (parser/policy); tck mirror branch updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…des, decline-branch pins

Gate anchored to query start (grammar admits constructors only there — n.graph
property no longer false-positives); start-nodes normalized eager (lazy join
silently declined the wavefront case); pins: property-string keyword masking,
n.graph acceptance, neq exactly-2 validation (E201), endpoint-mismatch decline,
9 direct-call decline shapes (changed-line coverage gate); tck mirror: manifest
reason synced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s, local-binding narrowing (Leo steer)

pattern_origin/pattern_neq getattr was self-inflicted (fields declared by this
branch) -> direct access; PatternElement.variable declared on both members;
n0/edge_op/n2 local binding (mypy narrows locals, not ops[i]) -> direct
ASTNode/ASTEdge fields; isinstance(pl.LazyFrame) over hasattr(collect);
_rows_base_graph() typed accessor contains the one sanctioned threading read;
_node Optional-narrowing; chain required params direct-indexed (safelist-
validated). 20 sites; audit research/dynamic-patterns-audit-L1.md (5 L kept,
4 M + broad-except narrowing recorded as follow-ups). dgx 1994+1344 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…module-per-family, Leo steer)

row_pipeline.py is the expression/projection lowering core; feature families get
their own modules (degrees.py precedent). Pure move + import updates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… split)

The gfql-polars audit fails any resolved target file missing from the
baseline (coverage_audit.py); the module-per-family split moved the pattern-
existence lowerings out of row_pipeline.py into pattern_apply.py without a
floor. dgx-measured 75.36% on the CI-verbatim CPU polars lane (identical on
this tree and the L2 tree); floor 67.0 per the file's ~8-below convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#1682's test_every_grammar_rule_is_exercised_by_the_corpus requires every
grammar rule to appear in DIFFERENTIAL_CORPUS. #1679's exists_subquery_atom
rule needs coverage — add representative EXISTS / NOT EXISTS queries (single
Earley derivation, so the semantic-ambiguity invariant still holds).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Any]] in pattern_apply

The semi-apply family's binding_ops carry the serialized-AST shape the rest of
the codebase already types as Dict[str, Any] (ast.py serialize_binding_ops,
row/pipeline.py). Match that; import Dict. Annotation-only, no behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmeyerov lmeyerov force-pushed the feat/gfql-cypher-exists branch from 08c1e01 to 889f59e Compare July 5, 2026 22:28
…ct / pl.DataFrame (no Any)

Replace the placeholder Any with the codebase's real types: serialized binding
ops are Dict[str, JSONVal] (the JSON type ASTSerializable.to_json/from_json use,
not Dict[str, Any]); _binding_ast_ops returns List[ASTObject]; the pattern
key-set helper returns Optional[pl.DataFrame] and _semi_apply_join_col takes a
pl.DataFrame. Any dropped from the imports. Annotation-only; mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Base automatically changed from dev/gfql-cypher-std-conformance to master July 5, 2026 23:10
@lmeyerov lmeyerov merged commit 9835cd0 into master Jul 5, 2026
75 checks passed
@lmeyerov lmeyerov deleted the feat/gfql-cypher-exists branch July 5, 2026 23:11
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