Skip to content

feat(gfql): native polars multi-source (node-cartesian) MATCH lowering (#1273)#1749

Merged
lmeyerov merged 3 commits into
masterfrom
feat/gfql-1273-multi-source-cartesian-polars
Jul 20, 2026
Merged

feat(gfql): native polars multi-source (node-cartesian) MATCH lowering (#1273)#1749
lmeyerov merged 3 commits into
masterfrom
feat/gfql-1273-multi-source-cartesian-polars

Conversation

@lmeyerov

Copy link
Copy Markdown
Contributor

What

Implements native polars lowering for multi-source MATCH — comma-separated disconnected aliases, e.g. MATCH (a {..}), (b {..}) RETURN a.id, b.id. Previously the polars rows(binding_ops) cartesian branch hard-declined (NotImplementedError); pandas handled it. New _cartesian_node_bindings_polars in row_pipeline.py reproduces the pandas per-alias schema exactly (bare alias id, alias.id, alias.<prop>, and the pandas alias.alias=True flag quirk incl. property-shadowing) and left-major cross-joins for row-order parity.

Two evidence-backed declines keep strict parity — both are shapes where pandas itself errors (proven on master), so both engines fail identically, never diverge:

  • Anonymous node op (MATCH (a),(b),()) — pandas raises on empty results.
  • ≥4 named aliases — pandas' bare-id merge residue collides (suffixes-duplicate-columns).

Verification (independent A/B, done by the reviewing agent — not just the author's self-report)

End-to-end g.gfql(...) vs the pandas oracle on the same graphs:

query polars vs pandas
RETURN a.id, b.id (2-source scalar) match
RETURN a.id AS ai, b.id AS bi (3-source aliased) match
RETURN count(*) AS c match
RETURN a, b (whole-entity) polars honest NIE (separate projection surface; pandas renders — decline, not divergence)
MATCH (a),(b),(c),(d) (≥4) both error (parity-safe decline)
  • Author's differential harness: ~10,000 lowered cases over 8 seeds → 0 disagreements, 0 suspicious, ~1,100 honest declines (all ≥4-named).
  • test_engine_polars_binding_rows.py: 41 passed (independently re-run). ruff: clean.
  • Regression check: the only failures in the broad polars cypher/rows suite are 5 ceiling/round conformance cases verified pre-existing on clean master c355660a (polars version drift), 0 new regressions. mypy: master baseline, 0 new.

Scope / follow-ups (not this PR)

Independent fork off master (touches row_pipeline.py only — no overlap with the #1741 chain stack or #1743). Refs #1273.

🤖 Generated with Claude Code

lmeyerov added a commit that referenced this pull request Jul 20, 2026
ops: Sequence[Any] -> Sequence[ASTObject] (call site passes List[ASTObject]; ASTObject
added to the TYPE_CHECKING import). DRY vs pandas assessed: the docstring documents the
mirror of _gfql_cartesian_node_bindings_row_table — cross-engine (pandas .merge vs polars
.join) sharing would add indirection without removing real duplication; the value is
parity via faithful column-contract mirroring. CHANGELOG [Development]/Fixed entry added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6dQEcjdazEnzuvuwf73ZL
lmeyerov and others added 3 commits July 20, 2026 11:24
#1273)

Multi-source MATCH like `MATCH (a {..}), (b {..})` lowers to a
`rows(binding_ops=[Node, Node, ...])` op whose node_cartesian mode was
previously deferred (returned None -> NotImplementedError) on the polars
engine. Implement it natively, mirroring the pandas
`_gfql_cartesian_node_bindings_row_table` oracle exactly:

- per-alias frame = filter_by_dict + project into the
  `_gfql_node_alias_lookup_frame` schema (bare `alias` id, `alias.id`,
  `alias.<prop>`, and the leaked named-op flag `alias.alias = True` that
  shadows a same-named real property), then a left-major cross join so the
  row order matches pandas' constant-key merge (no ORDER BY needed).
- Decline (honest NIE, no pandas bridge) outside pandas' reliable zone:
  anonymous node ops (pandas raises a spurious schema error on empty
  results) and >3 named aliases (pandas' bare-id merge residue collides
  on the 4th frame). Both engines then error, never diverge.

Verified by a differential fuzz (random small graphs x multi-source query
shapes incl. WHERE / ORDER BY / property-name-vs-alias collisions) against
the pandas oracle: 0 disagreements over ~10k lowered cases across seeds.
Adds parametrized parity + explicit pin tests. No new ruff/mypy findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6dQEcjdazEnzuvuwf73ZL
…rage gate)

Add an alias==node_id decline test (the one reachable defensive branch), and
pragma: no cover the genuinely-unreachable guards (post-materialize nodes present,
node_id is the bound column, non-str alias already filtered, defensive filter/collect
excepts, non-empty per_alias) with rationale — mirrors this repo's accepted approach
for structurally-uncoverable branches. 41 tests still pass, ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6dQEcjdazEnzuvuwf73ZL
ops: Sequence[Any] -> Sequence[ASTObject] (call site passes List[ASTObject]; ASTObject
added to the TYPE_CHECKING import). DRY vs pandas assessed: the docstring documents the
mirror of _gfql_cartesian_node_bindings_row_table — cross-engine (pandas .merge vs polars
.join) sharing would add indirection without removing real duplication; the value is
parity via faithful column-contract mirroring. CHANGELOG [Development]/Fixed entry added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6dQEcjdazEnzuvuwf73ZL
@lmeyerov
lmeyerov force-pushed the feat/gfql-1273-multi-source-cartesian-polars branch from 11a9d06 to 772a655 Compare July 20, 2026 18:25
@lmeyerov
lmeyerov merged commit 60ecaea into master Jul 20, 2026
57 checks passed
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