Skip to content

fix(gfql): crash family — polars filter helpers (#1882, #1913-f4) + nodes-only pandas Cypher (#1879) - #1942

Merged
lmeyerov merged 2 commits into
masterfrom
fix/gfql-1882-1879-crash-family
Aug 19, 2026
Merged

lmeyerov merged 2 commits into
masterfrom
fix/gfql-1882-1879-crash-family

Conversation

@lmeyerov

Copy link
Copy Markdown
Contributor

Pre-release crash-family fixes, all verified live at master e6625ed28 with hand-computed oracles.

#1882 — public filter_by_dict helpers crash on polars frames (FIXED — Fixes #1882)

filter_nodes_by_dict / filter_edges_by_dict / bare filter_by_dict on polars frames raised AttributeError: 'DataFrame' object has no attribute 'assign' (filter_by_dict.py built the mask with pandas idioms after resolve_engine routed POLARS). Fix: filter_by_dict now dispatches POLARS-resolved frames to the existing native filter_by_dict_polars (same column resolution via the shared resolve_filter_column, same typed E301/E302 errors, same 3VL membership semantics). Polars-in comes back polars — no silent engine swap; explicit engine='pandas' coercion is unchanged. Exotic predicates with no native polars lowering keep the engine's existing parity-or-error NIE contract (as GFQL polars chains already do).

The two secondary findings in the #1882 body (seeded_typed_hop lazy-frame decline, get_degrees nodes-only divergence) are NOT addressed here — #1882 can close on its headline defect per its title, or stay open if those are tracked under it.

#1913 finding-4 — same family on prune_self_edges (PARTIAL: finding 4 only)

prune_self_edges on polars edges hit polars' column-selecting boolean __getitem__: raw ValueError, and it would silently select COLUMNS whenever the row count equals the column count. Now polars-native (.filter expression, eager and lazy), preserving pandas' NaN != x -> keep null-endpoint row semantics; polars-in stays polars. The filter_nodes_by_dict/filter_edges_by_dict half of finding 4 falls out of the #1882 fix and is pinned. #1913 stays open: findings 1–3 (stale-index rebind resurrection, recovery-recipe failures, completeness-lock blind spots) are untouched here.

#1879 — Cypher/chain on nodes-only graphs (PARTIAL: pandas half + advice)

On a graph whose edges were NEVER bound, all pandas Cypher/chain died with a bare TypeError: 'NoneType' object is not subscriptable (ast.py:265 slicing g._edges[:0]), with or without a policy attached — the policy toggle was inert because both paths crashed. Now:

  • pandas serves node-only patterns (MATCH (n) … needs no edges): the chain executor synthesizes an internal empty edge frame for node-only op lists and drops it from the result (nodes-only in, nodes-only out; policy on/off agree, and the pins assert the hooks actually fire).
  • edge patterns decline typed: GFQLSchemaError E304 "Cannot traverse edges: graph has no edges bound" instead of the bare TypeError, on both the Cypher and chain() surfaces.
  • polars advice is no longer broken: polars keeps its typed NotImplementedError decline, and its "use engine='pandas'" guidance now points at an engine that serves — pinned live (the pin runs the recommended engine and checks the answer).
  • empty-but-BOUND edges kept working throughout — pinned on both engines.

#1879 stays open for the polars half: polars still declines node-only patterns on nodes-only graphs (typed, with working advice) rather than serving them; serving there means teaching _chain_traversal_polars / its node-only fast case to run without bound edge endpoints. ASTCall-bearing chains on nodes-only graphs also keep their pre-existing behavior (out of scope).

Pins & anti-vacuity

graphistry/tests/compute/gfql/test_crash_family_1882_1879.py (registered in bin/test-polars.sh POLARS_TEST_FILES): 30 tests, 19 red at master e6625ed28 / 11 both-sides pins green there; all 30 green at this head. Mutation checks: each fix piece reverted independently is caught (filter dispatch → 6 pins; prune polars branch → 1; chain synthesize → 10; typed decline → 2; result strip → 1).

Gates

  • ruff clean; comment-density / cypher-surface / type-hygiene guards rc=0
  • full-tree mypy: identical 4 pre-existing errors vs master (diff empty)
  • full graphistry/tests/compute failure SET identical to master's baseline apart from the new pins (details in checks); gfql/index/test_index.py (GFQL: legitimate .edges() rebind resurrects a STALE adjacency index (silent wrong answers, both engines) + documented recovery fails #1913 rebind/index contracts) failure set identical to master (its 38 cudf-param fails are environmental — libnvrtc.so.12 missing on this box, identical at master; pandas/polars all pass)
  • polars-lane registration for the new polars-mentioning test file (lane-completeness guard green); no new source files, so no coverage-baseline entries needed
  • cuDF spot-checked live for both fixes (dataframe ops lane; cupy-only lanes are broken on this box and were not run)

🤖 Generated with Claude Code

https://claude.ai/code/session_01AjbKuKheqDu78oapRT5AYm

lmeyerov and others added 2 commits August 18, 2026 16:05
- filter_by_dict: dispatch polars frames to the native filter_by_dict_polars
  (public filter_nodes_by_dict/filter_edges_by_dict/filter_by_dict crashed
  with AttributeError: 'assign'; polars-in comes back polars) (#1882)
- prune_self_edges: polars-native filter (boolean __getitem__ selects COLUMNS
  in polars); pandas null-endpoint semantics preserved (#1913 finding-4)
- chain: a nodes-only graph serves node-only chains via an internally
  synthesized empty edge frame (dropped from the result), and declines
  edge-op chains with a typed GFQLSchemaError instead of a bare TypeError
  at ast.py:265; policy on/off now agree (#1879 pandas half)
- pins: graphistry/tests/compute/gfql/test_crash_family_1882_1879.py
  (19 red at master e6625ed, 30 green here)

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AjbKuKheqDu78oapRT5AYm
@lmeyerov
lmeyerov merged commit f001cbb into master Aug 19, 2026
77 checks passed
@lmeyerov
lmeyerov deleted the fix/gfql-1882-1879-crash-family branch August 19, 2026 00:29
lmeyerov added a commit that referenced this pull request Aug 19, 2026
lmeyerov added a commit that referenced this pull request Aug 19, 2026
Lane-F regenerated the baseline from a pre-#1942 tree (chain.py 7 findings);
#1942 landed an 8th on master with its cap. The clean merge kept the stale
tighter caps, failing the merge ref — the two-green-PRs baseline-union hazard
in comment form. chain.py entries restored to master's values; every other
entry keeps this branch's downward-only movement.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AjbKuKheqDu78oapRT5AYm
pull Bot pushed a commit to admariner/pygraphistry that referenced this pull request Aug 19, 2026
…ph shapes (graphistry#1889)

`gfql_validate` returned `{ok: True, diagnostics: []}` on a graph whose frames were
never attached (`graphistry.bind(...)` names columns only), then execution died with a
bare `ValueError: Missing edges` (pandas/cuDF `materialize_nodes`) or an empty-message
`AssertionError` (polars `ensure_nodes_polars`). A preflight that approves what the
runtime always rejects is drift by construction.

Both sides now consult one predicate, `validate_graph_shape`:

* neither nodes nor edges bound -> GFQLSchemaError E305 `graph-not-bound` (new code),
  with a bind remedy -- raised by the validator AND by execution on every engine
  (pandas/cuDF via `validate_chain_schema`, polars via the pre-dispatch guard).
* edges unbound + an edge pattern -> E304, the same typed decline execution already
  gave (graphistry#1942); the validator no longer stays silent about it.
* nodes-only + node-only pattern stays admitted on both sides and keeps answering.

`schema=False` callers (remote preflight, which holds no local frames) skip the shape
check, so `chain_remote` is unaffected.

Pins: `test_validate_execute_agreement_1889.py` -- the 4 filed combos x {pandas, polars}
agreement matrix (10/18 red at master 0c3f3a1), typed-code pins on both surfaces,
value-level served-record oracles, and the not-flagged shapes (bound graph, edges-only,
schema=False). Each of the 5 fix sites was mutation-checked. `test_gfql_validate_only`'s
structural-only expectation moved to `schema=False`, which is what it was really pinning.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AjbKuKheqDu78oapRT5AYm
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.

bug(gfql): public filter_by_dict/filter_nodes_by_dict crash on polars frames (resolver says POLARS, mask builder is pandas-idiom)

1 participant