Repository navigation
refactor(gfql/cypher): switch to Earley parser; retire LALR pattern-shape cruft (#1031 slice 1) - #1217
Merged
Conversation
…cates EQ already accepts raw strings via inline ``_normalize_value`` + ``_validate_fields`` overrides. The other comparison ops lacked the same overrides and rejected raw strings unconditionally — their ``__call__`` methods also explicitly raised ``TypeError`` on strings. This was latent: LALR couldn't unify label-predicate and property-predicate alternatives in Cypher's ``where_predicates``, so queries like ``WHERE n.name <> 'value' AND n:Label`` routed through raw expr text and never reached ``ComparisonPredicate.__call__``. #1031's Earley swap unifies the route, surfacing the strictness on idiomatic Cypher patterns. Pandas Series supports lexicographic ``>``/``<``/``!=`` on strings natively, so the fix is to extend EQ's pre-existing string acceptance to siblings. Implementation: - Extract ``_StringAllowingComparisonMixin`` with the ``_normalize_value`` + ``_validate_fields`` overrides. - Apply mixin to NE/GT/LT/GE/LE. - Extend each ``__call__`` to handle ``isinstance(self.val, str)``. The strict raw-string rejection on the base ``ComparisonPredicate`` stays — it still applies to ``Between`` (and any future direct-IR constructors) where the datetime-vs-string ambiguity matters for direct-IR users. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…hape cruft (#1031 slice 1) Per tech-lead direction (1-day timebox) following an LALR(1) state-collapse on ``<comparable> AND <pattern>`` shapes that could not be resolved by grammar restructure. Earley parser swap eliminates four LALR-induced workarounds: 1. The 3 dedicated pattern-shape ``where_clause`` grammar alternatives (``where_pattern_and_expr_clause`` / ``expr_and_where_pattern_clause`` / ``where_pattern_only_clause``) — collapse to the unified ``expr`` route via a new ``WHERE_PATTERN -> pattern_atom`` leaf in ``?primary``. 2. ``_canonicalize_where_single_pattern_and_expr`` — regex source-rewrite that reordered ``expr AND pattern AND expr`` to ``pattern AND <rest>`` so the LALR grammar could match. Earley accepts patterns at any AND-spine position natively. 3. ``_mixed_where_pattern_expr_error`` — pre-flight rejector for shapes LALR could parse but the IR couldn't represent. Replaced with a structural lift step that inspects the parsed boolean tree. 4. The ``except LarkError`` retry block in ``parse_cypher`` that fed the canonicalize-then-reparse path. What this PR adds: - ``BooleanExpr.op`` literal extended with ``"pattern"``; new ``BooleanExpr.pattern: Optional[Tuple[PatternElement, ...]]`` field. - Parser ``pattern_atom`` transformer: wraps ``WHERE_PATTERN`` token as ``BooleanExpr(op="pattern", pattern=parsed)``; reuses the existing ``_parse_where_pattern_predicate_text`` helper. - ``_split_top_level_and_pattern_leaves`` walker + ``_rebuild_and_tree`` helper + ``_build_where_with_pattern_lift`` that extracts pattern leaves from ``expr_tree`` into ``WhereClause.predicates`` as ``WherePatternPredicate`` entries before lowering. - Parser swap: ``Lark(parser="earley")`` for both ``_parser`` and ``_pattern_parser``. What this PR retires: - 3 grammar alternatives, 3 transformer methods, 1 helper (``_mixed_where_clause``), 2 pre-flight functions, 3 derived regex constants (``_WHERE_PATTERN_SEQUENCE_RE``, ``_WHERE_PATTERN_THEN_EXPR_RE``, ``_WHERE_EXPR_THEN_PATTERN_RE``), 2 helper utilities (``_WHERE_CLAUSE_BODY_RE``, ``_BOOLEAN_KEYWORD_RE``, ``_line_and_column_from_offset``), and the ``parse_cypher`` ``except LarkError`` retry block. Net diff: +204 / -144. Strict-improvement consequences (Earley accepts what LALR rejected): - ``WHERE expr OR expr`` now parses as a structured ``or`` tree. - ``WHERE expr AND (expr OR expr)`` (Shape B) now parses as ``and(left, or(...))``. - ``WHERE n:Label AND n.prop = X`` now routes through structured ``where_predicates`` (was raw expr); label narrowing applies. - ``WHERE prop OP 'string'`` mixed with label predicates now hits ``ComparisonPredicate`` for strings — separately fixed in the preceding commit by extending EQ's string-accepting overrides to NE/GT/LT/GE/LE. #1031 milestones in this PR: - ✅ Retires ``_canonicalize_where_single_pattern_and_expr``. - ✅ Removes one of three ``split_top_level_and`` callers (parser side). Remaining caller: ``predicate_pushdown._split_conjuncts``, retired separately under #1200. Together this brings ``expr_split.py`` deletion within reach. - ⏭️ ``NOT pattern`` lowering (anti-semi-join) — slice 2 territory; emits explicit ``unsupported`` error today. - ⏭️ Multi-positive patterns — slice 3 territory; rejected at lift step. - ⏭️ ``OR``/``XOR`` around patterns — slice 4 territory; rejected at lift step. Tests updated: - 7 tests adjusted to match Earley's strict-improvement routing (``test_or_where_not_yet_supported``, conformance Shape B parser + binder, conformance Shape F parser + binder, label-narrowing mixed case, logical planner WITH-stage attachment). - ``test_parse_supports_where_pattern_predicate_and_expr_mix`` paren- preservation case updated to expect ``boolean_expr_to_text``- reconstructed paren-stripped form (semantically equivalent). 1539 / 1539 GFQL tests pass. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…ngs entries Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…#1217) Pre-#1217: LALR rejected OR / NOT inside WHERE bodies, so disjunctive and negation shapes had zero native end-to-end test coverage. Earley accepts them and the runtime evaluates via pandas, but until now the correctness was implicit — only the (separate-repo) tck-gfql harness exercised these paths via xfail-marked scenarios. Adds 5 native execution tests in test_lowering.py: - ``test_string_cypher_executes_disjunctive_property_predicate_returns_union`` mirrors TCK match-where1-10: ``WHERE n.p1 = 12 OR n.p2 = 13`` returns the union of matches (a, b), not just one. - ``test_string_cypher_executes_disjunctive_same_alias_property_predicate`` ``WHERE n.p1 = 12 OR n.p1 = 99`` — same alias on both sides. - ``test_string_cypher_executes_negation_property_predicate_returns_complement`` ``WHERE NOT n.p1 = 12`` — locks pandas's NaN-NOT-NaN semantics (NaN is falsy under NOT, so rows with null p1 are excluded). - ``test_string_cypher_executes_disjunctive_then_conjunction`` ``WHERE (n.p1 = 12 OR n.p2 = 13) AND n.id = 'a'`` — mixed OR-AND tree narrowing. - ``test_string_cypher_executes_disjunction_returns_correct_count_with_more_rows`` larger fixture (6 rows) confirming OR doesn't silently union too many or too few rows. Pairs with the tck-gfql sibling branch ``issue-1031-grammar-mixed-where-pattern-expr`` which registers ``match-where1-10`` as ``success_matches_expected`` in the xfail-contract (kept as ``xfail`` rather than promoted to ``supported`` until broader OR-disjunction conformance is validated). Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Contributor
Author
|
Filed follow-up: #1219 — "Cypher WHERE: static validation gap on row-boolean shapes (OR/NOT/XOR among row predicates) — emergent from #1217 Earley swap". The validator question is genuinely out of scope for #1031 (which is specifically about mixing pattern + row predicates). Earley's broader unification incidentally lifts the implicit LALR rejection on row-side OR/NOT/XOR, exposing a missing semantic gate that pre-#1217 wasn't needed. Decision space (validator vs partial-accept vs full-disjunction-support vs defer-with-docs) is left open in #1219. |
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
IMPORTANT findings from multi-dimension review:
1. Dead-code in OR test (test_lowering.py:3199-3205) — the
``if False else`` made the first comprehension permanently dead;
the fallback branch then asserted on either of two contradictory
shapes. Replaced with a single deterministic assertion: 5 rows
expected, every kept row carries either ``p1: 1`` or ``p2: 2`` in
its rendered form.
2. EQ duplicated the new mixin's overrides (comparison.py:245-263).
The mixin docstring already invited the retrofit ("EQ predates this
mixin with an inline override"). EQ now uses
``_StringAllowingComparisonMixin`` for its single-source contract.
SUGGESTIONS deferred (documented in plan):
- NaN-NOT semantics test name (locks pandas-not-Cypher behavior;
acknowledged in test docstring + #1219 link).
- ``_rebuild_and_tree`` invents source-spans across non-adjacent
residuals (cosmetic, error-reporting only).
- No deeply-nested pattern-under-multi-level-boolean test
(``_has_pattern_descendant`` handles correctly by inspection).
- PEP-8 spacing nit after constant deletes.
- Defensive ``_split_top_level_and_pattern_leaves`` bad-flag drop on
malformed AND nodes (#1214 invariant prevents the case).
1680 GFQL + predicates tests pass.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This was referenced Apr 26, 2026
…mixed-where-pattern-expr # Conflicts: # CHANGELOG.md # graphistry/compute/gfql/cypher/parser.py # graphistry/tests/compute/gfql/cypher/test_where_bool_conformance.py
…der Earley ambiguity (#1031) Earley with ambiguous grammar parsed `MATCH ... WITH ... WHERE ... RETURN` two ways: (a) `with_stage[with_clause + with_where_clause]` (correct), or (b) `stage[with_clause] + standalone where_clause` (wrong — query_body then attached the where to the preceding MATCH at parser.py:1582-1587). Earley's default ambiguity resolution silently picked the wrong parse: the predicate ended up populated on both matches[0].where and top-level where, with with_stages[0].where = None. Surfaced by Wave 2 fresh-eyes review of #1217 — the renamed/loosened test_logical_planner_applies_predicates_attached_to_with_stage_filter masked the regression. Fix: bump Lark rule priority on with_where_clause so Earley prefers it during ambiguity resolution. Smoke-trace post-fix: match.where=0 preds, top.where=0 preds, with[0].where=ExpressionText(...) Validated on dgx-spark: - graphistry/tests/compute/gfql/cypher/ → 1038 passed - graphistry/tests/compute/gfql/ → 1543 passed - graphistry/tests/compute/ → 2441 passed Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ring mixin Two Wave-2 review fixes: 1. test_logical_planner_applies_predicates_attached_to_with_part — restored original test name + WITH-part predicate assertion. The previous rename (..._to_with_stage_filter) and "predicate anywhere in plan" loosening had MASKED the WITH-WHERE re-routing regression that the previous commit (parser priority bump) fixed. Now the binder correctly attaches the predicate to the WITH query_part, so the original assertion holds again. 2. New test_comparison_strings.py — direct unit tests for _StringAllowingComparisonMixin on EQ/NE/GT/LT/GE/LE. Each op verified for: validate() acceptance of raw-string val, JSON round-trip preserving string val, lexicographic Series application matching pandas semantics. Closes the wave-2 IMPORTANT gap that only end-to-end coverage existed for these ops. Validated on dgx-spark: graphistry/tests/compute/ → 2458 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…p tests DRY pass on #1217 surface area: - parser.py: drop pre/post-#1031 narration on _split_top_level_and_pattern_leaves, _has_pattern_descendant, _rebuild_and_tree, _build_where_with_pattern_lift, pattern_atom, and the Earley parser comment. Replace defensive 'pattern_atom invariant' branch with an assert. - test_lowering.py: trim 9-line OR/NOT section header + per-test docstrings to one structural line each; pandas NaN/Cypher semantics covered once at the section level. - test_parser.py / test_where_bool_conformance.py: drop 'Pre-#1031 LALR / Post-#1031 Earley' history comments — assertions speak for themselves. - test_comparison_strings.py: parametrize the per-op lex-apply tests via a callable→expected-list mapping; 5 near-identical functions → 1 parametrized test. Net -140 lines; no behavior change. Validated on dgx-spark: 1078 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Adds 4 native end-to-end tests locking the Earley-admitted row-boolean shapes that were previously deferred to #1219: 1. cross_alias_or_returns_correct_union — `WHERE a.x = 1 OR b.y = 2` against a fixture where wrong-rows would surface if the OR were silently split past a join. Pushdown correctly leaves the OR intact; 2 expected rows returned. 2. optional_match_or_with_existing_validator — `OPTIONAL MATCH ... WHERE ... OR b IS NULL` raises a `GFQLValidationError` from the existing OPTIONAL-MATCH-projection validator before WHERE evaluates. Locks the gate fires regardless of OR shape. 3. mixed_type_or_via_ast_evaluator_gate — `WHERE n.var > 'te' OR n.var > 0` against a mixed-type Series. The row-where AST evaluator emits an explicit `GFQLTypeError` rather than silently propagating pandas's raw TypeError. Locks the loud-failure mode. 4. homogeneous_or_returns_correct_union — sanity check that simple homogeneous-type OR continues working post-#1217. CHANGELOG entry rewritten: removes "complex compositions ... unverified" language; replaces with empirical coverage summary. #1219 still tracks the deeper compositional frontier beyond current fixtures. Validated on dgx-spark: 1547 passed (1543 + 4 new). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…onesty Wave-3 review on commit aacb2a6 surfaced two IMPORTANTs: 1. Shape 2 test name + docstring overstated what it gates. The OPTIONAL-MATCH-projection validator fires regardless of WHERE shape (or absence of WHERE). Renamed to *_seed_only_projection, parametrized over `("", "WHERE b.x = 1", "WHERE b.x = 1 OR b IS NULL")` so the test now empirically *proves* OR-equivalence — the gate fires identically across all three WHERE shapes. 2. Shape 3 narrative implied a "type-mismatch gate"; in reality the call executor wraps any non-typed exception as GFQLTypeError(E303, "unsupported row expression"). Tightened pytest.raises to drop the fallback `TypeError` (unreachable empirically) and pin the substring "unsupported row expression" so a future regression that drops the wrapper actually fails the test. CHANGELOG language softened accordingly: "the call executor wraps pandas's TypeError as GFQLTypeError(E303) via the generic unsupported-row-expression path". Validated on dgx-spark: 1549 passed (1547 + 2 from shape-2 parametrize). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ixture, CHANGELOG accuracy Wave-4 (targeted-amplification + full-fresh-eyes) found 6 IMPORTANTs. Addressed: 1. Shape-2 `match="OPTIONAL MATCH"` could match any of 6 distinct error sites — pinned to "seed alias are not yet supported" so the test cannot drift onto an unintended validator. 2. Shape-3 `match="unsupported row expression"` was an inner-ValueError substring with 30+ emit sites in the row pipeline — pinned to the stable error code "invalid-node-reference" (E303) instead, which is the actual contract the wrapper enforces. 3. Cross-alias-OR fixture (test_string_cypher_executes_cross_alias_or_*) used 1:1 join topology where multiple wrong-pushdown variants coincidentally produce the same answer. Rebuilt with 2:2 join so wrong variants discriminate. Expected rows changed from 2 to 3. 4. 5-row OR test (`test_string_cypher_executes_disjunction_returns_correct_count_with_more_rows`) used `len == 5` + `all(... in r ...)` — could pass on wrong row identities. Tightened to sorted-id assertion. 5. CHANGELOG drift — claimed 1539 tests + only `match-where1-10` changed on tck-gfql side. Rewrote to 1549 + the actual 8-refinement contract scope (TYPE_ERROR_KEYS, MATCHES_EXPECTED, WRONG_ROW_KEYS, PROMOTION demotion). 6. Coverage gap — `test_parse_rejects_mixed_where_pattern_predicates_as_unsupported` only covered variable-length patterns (`-[:R*]->`). Added the simple non-variable-length variant so future slice 2/3/4 lifts can't silently regress the common case. Validated on dgx-spark: 1551 passed (was 1549; +2 from non-VL params). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Wave-5 SUGGESTION: the +2 non-VL parametrize entries from 912a4ac brought the count to 1551 but the CHANGELOG still said 1549. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…mixed-where-pattern-expr
This was referenced Apr 28, 2026
lmeyerov
added a commit
that referenced
this pull request
Apr 28, 2026
…ttern, terser comments Wave-2 (2a targeted + 2b broad) found 1 IMPORTANT + several SUGGESTIONs. IMPORTANT (Wave-2b): - expr_split.split_top_level_and docstring overstated the topology argument. Original draft claimed distribute-OR breaks on fan-out topologies; the cross-alias-OR test in test_lowering.py:3206-3211 (added in #1217) explicitly confirms distribute-OR converges to correct row-set semantics on a 2:2 fan-out fixture. The actual reason pushdown leaves OR opaque is that the multi-alias references on the conjunct cause it to be retained post-join. Rewrote docstring to describe the real mechanism + cite the test. SUGGESTIONs (Wave-2a + 2b): - _ids_for(graph, query) helper inlined to match the established inline-sorted-comprehension style used elsewhere in test_lowering.py (12+ existing call sites). Removes inconsistency within this PR. - Pattern-op branch comment in _boolean_expr_text.py compressed from 9 lines to 3 — the verbose explanation duplicated the test docstring. - Local-variable assignment for graph.gfql() result kept (matches existing test patterns + works around a Plottable.gfql pyright attribute warning that fires on fluent chains). Test counts unchanged: 8 new tests; full gfql suite 1581 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2 tasks done
lmeyerov
added a commit
that referenced
this pull request
Apr 28, 2026
…, mirrored-guard clarification Wave-4 (4a targeted + 4b from-scratch) — 0 BLOCKER + 0 IMPORTANT, 9 SUGGESTIONs. Picked up the cheapest valuable ones: - Wave-4b S1: mixed-string-numeric AND-inside-OR test claimed to exercise _StringAllowingComparisonMixin but used `n.s = 'a'` (plain EQ, supported pre-#1217). Swapped to `n.s > 'a'` (string GT, the mixin-specific path); flipped fixture s-values to keep the same truth-table outcomes. - Wave-4b S3: added test_string_cypher_executes_xor_with_null_uses_three_valued_logic — sibling to the OR/NOT 3VL test, locks NULL XOR T = NULL. Reuses the 3VL fixture from the De Morgan tests. - Wave-4a S1: `_split_conjuncts` mirrored guard now names the actual failure mode (`_combine_conjuncts` AND-joins residuals) before pointing to the fuller rationale in expr_split. Skipped (out of scope or duplicate): - Wave-4b S2: rightmost-only discriminator comment is already honest; test verified correct. - Wave-4b S4: cross-alias OR / cross-product fixture — already covered by test_string_cypher_executes_cross_alias_or_returns_correct_union (#1217). - Wave-4b S6: optional CHANGELOG bullet for test+docs PR. - Wave-4a S2 (OR-analyzer in pushdown_safety.py:58-60): pre-existing unrelated, separate followup. Test counts: 10 new tests; full gfql suite green (1583 passed). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
lmeyerov
added a commit
that referenced
this pull request
Apr 29, 2026
…ils (#1227) * test+docs(#1219): residual row-boolean compositional matrix + guardrails Worker B / independent-hardening stream of #1219. After #1217's Earley swap surfaced row-boolean shapes (OR/NOT/XOR among row predicates) that LALR rejected, four compositional shapes remained unverified beyond the fixtures #1217 covered. This PR locks empirical correctness across the residual matrix + adds two lightweight guardrail comments. ## Compositional matrix tests (test_lowering.py) All four shapes verified correct empirically; locked with sorted-id assertions against discriminating fixtures: 1. **Nullable NOT/OR** — `WHERE NOT n.x = 1 OR n.y IS NULL` against a 4-row fixture mixing real and projected nulls. Locks pandas-backed row-evaluator preserves Cypher 3VL truth table (NULL OR T = T): `{n2, n4}`. 2. **N-ary OR (3 branches)** — `WHERE n.x = 1 OR n.x = 2 OR n.x = 3`. Locks left-associative parse `or(or(=1, =2), =3)` doesn't degenerate under associativity bugs. `{n1, n2, n3}`. 3. **De Morgan compositions** (parametrized × 4) — both `NOT(A OR B)` ≡ `NOT-A AND NOT-B` and `NOT(A AND B)` ≡ `NOT-A OR NOT-B` against a 4-row fixture covering all (x∈{1,2}, y∈{2,3}) combos. Each form and its De-Morganed equivalent return the same row set. 4. **Mixed-string-numeric AND inside OR** — `WHERE (n.s = 'a' AND n.x > 0) OR n.x < -1`, exercising `_StringAllowingComparisonMixin` (#1217) paired with OR composition. `{n1, n2, n4}`. ## Guardrails - `expr_split.py::split_top_level_and` — added load-bearing AND-only docstring with #1219 cross-ref explaining why a sibling `split_top_level_or` would break OR-distributivity-over-join correctness. No future maintainer should accidentally add it without first redesigning topology-aware pushdown safety. - `_boolean_expr_text.py::boolean_expr_to_text` — added explicit `if expr.op == "pattern"` branch with docstring. Currently unreachable in production (lift step extracts pattern leaves before the binder walks the tree) but documents the contract: emit the raw pattern source rather than silently falling through to empty string. ## Test impact Validated on dgx-spark: `graphistry/tests/compute/` → 2524 passed (7 new tests; remaining delta from baseline absorbed by #1224's unrelated additions). Closes the residual frontier portion of #1219. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * test+docs(#1219): wave-1 review fixes — discriminating fixtures, equivalence assertions, pattern-op unit test, mirrored guard Wave-1 review on c196393 surfaced 3 IMPORTANTs + 5 SUGGESTIONs. Addressed: 1. N-ary OR test now has a companion (duplicate-leftmost-branch) that isolates the rightmost-drop associativity bug from the any-branch- drop case. Comment rephrased to be honest about what the original test covers (any branch dropped, not specifically rightmost). 2. De Morgan parametrize restructured: paired (compound, distributed, expected) tuples instead of independent rows. Now asserts: - compound matches expected - distributed matches expected - compound == distributed (the actual De Morgan equivalence) Added separate double-negation test (NOT NOT A ≡ A). 3. New test_boolean_expr_to_text_emits_atom_text_for_pattern_op in test_boolean_expr.py exercises the explicit pattern branch added in c196393. Locks the contract even though the branch is currently unreachable in production (lift step extracts pattern leaves before the binder walks the tree). 4. Mirrored AND-only guard comment near _split_conjuncts in predicate_pushdown.py — that's where future maintainers actually look when adding pushdown features; the load-bearing rationale stays in expr_split.py's docstring. Test counts on dgx-spark: 2525 passed (was 2524 + 1 pattern_op unit test; net 8 new tests in PR diff vs master). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * test+docs(#1219): wave-2 review fixes — accurate docstring, inline pattern, terser comments Wave-2 (2a targeted + 2b broad) found 1 IMPORTANT + several SUGGESTIONs. IMPORTANT (Wave-2b): - expr_split.split_top_level_and docstring overstated the topology argument. Original draft claimed distribute-OR breaks on fan-out topologies; the cross-alias-OR test in test_lowering.py:3206-3211 (added in #1217) explicitly confirms distribute-OR converges to correct row-set semantics on a 2:2 fan-out fixture. The actual reason pushdown leaves OR opaque is that the multi-alias references on the conjunct cause it to be retained post-join. Rewrote docstring to describe the real mechanism + cite the test. SUGGESTIONs (Wave-2a + 2b): - _ids_for(graph, query) helper inlined to match the established inline-sorted-comprehension style used elsewhere in test_lowering.py (12+ existing call sites). Removes inconsistency within this PR. - Pattern-op branch comment in _boolean_expr_text.py compressed from 9 lines to 3 — the verbose explanation duplicated the test docstring. - Local-variable assignment for graph.gfql() result kept (matches existing test patterns + works around a Plottable.gfql pyright attribute warning that fires on fluent chains). Test counts unchanged: 8 new tests; full gfql suite 1581 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * test+docs(#1219): wave-3b fixes — accurate split-OR rationale + XOR runtime sibling Wave-3b from-scratch fresh-eyes review found 1 IMPORTANT + 3 SUGGESTIONs. Addressed: - IMPORTANT: split_top_level_and docstring's stated mechanism ('multi-alias refs cause retention post-join') was misleading. Real mechanism: pushdown silently AND-recombines the pushed conjuncts inside PatternMatch.predicates. AND distributes (split + AND-recombine ≡ original); OR does not (split + AND-recombine ≠ original). An OR-aware split would need a UNION-of-pushed-branches recombine path the current pipeline doesn't implement. Rewrote the docstring with the accurate split + AND-recombine rationale. - SUGGESTION 3: XOR runtime row-set test (sibling to OR/AND/NOT tests this PR adds). Locks symmetric-difference semantics. Skipped: - SUGGESTION 4 (NOT IS NULL standalone): nullable_not_or already exercises IS NULL composed with NOT/OR; standalone marginal. - SUGGESTION 5 (cuDF parametrize): real scope creep, defer. Test counts: 9 new tests; full gfql suite green. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * test+docs(#1219): wave-4 polish — accurate mixin claim, XOR with NULL, mirrored-guard clarification Wave-4 (4a targeted + 4b from-scratch) — 0 BLOCKER + 0 IMPORTANT, 9 SUGGESTIONs. Picked up the cheapest valuable ones: - Wave-4b S1: mixed-string-numeric AND-inside-OR test claimed to exercise _StringAllowingComparisonMixin but used `n.s = 'a'` (plain EQ, supported pre-#1217). Swapped to `n.s > 'a'` (string GT, the mixin-specific path); flipped fixture s-values to keep the same truth-table outcomes. - Wave-4b S3: added test_string_cypher_executes_xor_with_null_uses_three_valued_logic — sibling to the OR/NOT 3VL test, locks NULL XOR T = NULL. Reuses the 3VL fixture from the De Morgan tests. - Wave-4a S1: `_split_conjuncts` mirrored guard now names the actual failure mode (`_combine_conjuncts` AND-joins residuals) before pointing to the fuller rationale in expr_split. Skipped (out of scope or duplicate): - Wave-4b S2: rightmost-only discriminator comment is already honest; test verified correct. - Wave-4b S4: cross-alias OR / cross-product fixture — already covered by test_string_cypher_executes_cross_alias_or_returns_correct_union (#1217). - Wave-4b S6: optional CHANGELOG bullet for test+docs PR. - Wave-4a S2 (OR-analyzer in pushdown_safety.py:58-60): pre-existing unrelated, separate followup. Test counts: 10 new tests; full gfql suite green (1583 passed). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * test+docs(#1219): wave-5 polish — extract 3VL fixture helper, clarify pattern-op unreachability Wave-5 formal review (multi-dim per .agents/skills/review/SKILL.md + adversarial pressure-test) found 0 BLOCKER + 0 IMPORTANT, 3 SUGGESTIONs of which 2 were actionable + cheap: - Extract `_three_valued_logic_fixture_graph()` helper paralleling `_de_morgan_fixture_graph()` — eliminates byte-identical 4-row NaN- mixed fixture duplication between nullable_not_or and xor_with_null. - Clarify pattern-op branch comment in `_boolean_expr_text.py` to name BOTH unreachability paths (top-level AND lift + nested-NOT/OR/XOR E108 rejection) instead of just the first. - Adversarial-rejected: malformed-NOT-chain test would be a tautology (right-recursive grammar makes depth-N equivalent to depth-2; existing double-negation test already exercises the path). Test counts unchanged: 11 new tests; full gfql suite 1583 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * docs(changelog): add #1227 row-boolean residual matrix entry Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> --------- Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
2 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Slice 1 of N for issue #1031. Per tech-lead direction (1-day timebox) following an LALR(1) state-collapse on ` AND ` shapes that could not be resolved by grammar restructure within Lark's LALR(1) constraints.
Earley parser swap eliminates four LALR-induced workarounds:
Net diff: +341 / -186 across 7 files.
Two commits, logically separate
Strict-improvement consequences (Earley accepts what LALR rejected)
What this PR adds
What this PR retires
#1031 progress
Test plan
Notes for review
🤖 Generated with Claude Code