What
rows(binding_ops=..., alias_prefilters=...) narrows the bindings table on pandas and cuDF and is silently ignored on the native polars engines. Same query, same binding_ops, different row count.
The generic path threads the hint into _gfql_binding_ops_row_table. The native polars row op calls its bindings builder with binding_ops and attach_prop_aliases only, so alias_prefilters never reaches it.
Repro
Fixture and assertions are already in the tree — graphistry/tests/compute/gfql/test_rewrite_param_discard.py::test_alias_prefilters_are_honoured_by_the_bindings_builder, a 12-node / 12-edge graph:
binding_ops = serialize_binding_ops([n(name="a"), e_forward(name="r"), n(name="b")])
g.gfql([rows(binding_ops=binding_ops,
alias_prefilters={"a": [{"kind": "expr", "text": "a.id < 3"}]})], engine=ENGINE)
pandas / cudf: 5 rows, max(a.id) < 3
polars / polars-gpu: 12 rows — the whole unfiltered bindings table
Severity
Bounded but real. The Cypher lowering that emits alias_prefilters always keeps the equivalent post-join filter, so Cypher-generated plans agree across engines and only lose the pushdown. Hand-written GFQL that passes alias_prefilters without a matching post-filter gets engine-dependent answers with no error. That is the silent-divergence class, just reachable only from the GFQL surface.
Why filed now
The gap was known when #1790 landed and was pinned as a pytest.mark.xfail(strict=True) with a prose reason and no issue number, so there was nothing to close and nothing to find. Recording it here so the xfail can reference it and so widening the polars builder has a home.
Fixing it means porting the prefilter evaluator to the native polars builder — a feature, not a patch, which is why #1790 correctly declined to bundle it.
Pinned
test_alias_prefilters_are_honoured_by_the_bindings_builder — strict xfail on polars and (as of the CB3 engine-parametrization) on polars-gpu, which is the same builder. When the builder learns the param, both markers must be deleted.
What
rows(binding_ops=..., alias_prefilters=...)narrows the bindings table on pandas and cuDF and is silently ignored on the native polars engines. Same query, samebinding_ops, different row count.The generic path threads the hint into
_gfql_binding_ops_row_table. The native polars row op calls its bindings builder withbinding_opsandattach_prop_aliasesonly, soalias_prefiltersnever reaches it.Repro
Fixture and assertions are already in the tree —
graphistry/tests/compute/gfql/test_rewrite_param_discard.py::test_alias_prefilters_are_honoured_by_the_bindings_builder, a 12-node / 12-edge graph:pandas/cudf: 5 rows,max(a.id) < 3polars/polars-gpu: 12 rows — the whole unfiltered bindings tableSeverity
Bounded but real. The Cypher lowering that emits
alias_prefiltersalways keeps the equivalent post-join filter, so Cypher-generated plans agree across engines and only lose the pushdown. Hand-written GFQL that passesalias_prefilterswithout a matching post-filter gets engine-dependent answers with no error. That is the silent-divergence class, just reachable only from the GFQL surface.Why filed now
The gap was known when #1790 landed and was pinned as a
pytest.mark.xfail(strict=True)with a prose reason and no issue number, so there was nothing to close and nothing to find. Recording it here so the xfail can reference it and so widening the polars builder has a home.Fixing it means porting the prefilter evaluator to the native polars builder — a feature, not a patch, which is why #1790 correctly declined to bundle it.
Pinned
test_alias_prefilters_are_honoured_by_the_bindings_builder— strict xfail onpolarsand (as of the CB3 engine-parametrization) onpolars-gpu, which is the same builder. When the builder learns the param, both markers must be deleted.