g.gfql([n({"id": X}), e_forward({"type": "HAS_CREATOR"}), n({"type": "Person"})], engine="polars") runs the full lazy forward/backward/combine traversal, while the identical Cypher MATCH (m {id: X})-[:HAS_CREATOR]->(p:Person) RETURN p is served by the seeded typed-hop fast path.
Measured locally on a 300k-node table with 27 object columns (RTX 3080 Ti box, best of 5 warm, not a published number): polars native chain 9.5 ms vs Cypher 2.7 ms; pandas native chain 1.8 ms (pandas takes chain._try_chain_fast_path).
Where it declines: graphistry/compute/gfql/lazy/engine/polars/chain.py, the index consult (~line 982) and the eager single-hop shortcut (~line 1006) both require _plain_edge, i.e. edge_match is None, and an unfiltered destination node. A typed edge (edge_match={"type": ...}) or a destination filter therefore falls through to the full traversal.
Fix shape: relax _plain_edge to allow scalar edge_match and a scalar destination filter_dict, or call chain_fast_paths._seeded_typed_return_dst_polars there and rebuild (nodes, edges) plus alias tags the way _tag_fast_path_aliases does on pandas. Keep test_seeded_typed_hop_fastpath.py::TestPolarsFastPathGates (LazyFrame declines, mixed-engine frames decline, varlen declines).
Found while writing graphistry/tests/compute/gfql/test_gfql_latency_contract.py (#2031), which for this reason pins Cypher against plain frame ops rather than the native chain.
g.gfql([n({"id": X}), e_forward({"type": "HAS_CREATOR"}), n({"type": "Person"})], engine="polars")runs the full lazy forward/backward/combine traversal, while the identical CypherMATCH (m {id: X})-[:HAS_CREATOR]->(p:Person) RETURN pis served by the seeded typed-hop fast path.Measured locally on a 300k-node table with 27 object columns (RTX 3080 Ti box, best of 5 warm, not a published number): polars native chain 9.5 ms vs Cypher 2.7 ms; pandas native chain 1.8 ms (pandas takes
chain._try_chain_fast_path).Where it declines:
graphistry/compute/gfql/lazy/engine/polars/chain.py, the index consult (~line 982) and the eager single-hop shortcut (~line 1006) both require_plain_edge, i.e.edge_match is None, and an unfiltered destination node. A typed edge (edge_match={"type": ...}) or a destination filter therefore falls through to the full traversal.Fix shape: relax
_plain_edgeto allow scalaredge_matchand a scalar destinationfilter_dict, or callchain_fast_paths._seeded_typed_return_dst_polarsthere and rebuild(nodes, edges)plus alias tags the way_tag_fast_path_aliasesdoes on pandas. Keeptest_seeded_typed_hop_fastpath.py::TestPolarsFastPathGates(LazyFrame declines, mixed-engine frames decline, varlen declines).Found while writing
graphistry/tests/compute/gfql/test_gfql_latency_contract.py(#2031), which for this reason pins Cypher against plain frame ops rather than the native chain.