Skip to content

GFQL polars: variable-length binding rows diverge from pandas (bounded min_hops>=3; undirected *1..1 halves; undirected filtered-seed over-counts) #1787

Description

@lmeyerov

Three silent-wrong divergences in the native polars rows(binding_ops=...) variable-length path, all found by differential fuzzing against the pandas oracle while reviewing #1781. All three reproduce on master 84835988 — they are not that PR's regressions. Pandas is the oracle and the contract is parity-or-NotImplementedError; these return a different answer with no error.

Grouped in one issue because the first two share a root cause family (the raw-edge reconstruction vs pandas' hop-window pruning) and all three are in the same gate.

1. Bounded directed min_hops >= 3 returns wrong counts

nodes 0..6
edges 4>6, 0>4, 1>2, 4>6, 5>6, 4>5, 4>6
MATCH (a)-[*3..3]->(b) RETURN count(*)     pandas 0   polars 1

Same cause as the unbounded -[*k..]-> case #1781 just gated: compute/hop.py:628 returns empty when max_reached_hop < min_hops, and :636+ prunes edges labelled below min_hops, while the polars arm rebuilds pairs from the raw matching edge table. max_reached_hop is a dedup-by-node BFS eccentricity, not a longest-walk length. #1781 declines the unbounded half; the bounded half is still served.

2. Undirected -[*1..1]- / -[*1]- halves the count

nodes 0..4
edges (1,2)(1,2)(0,2)(1,0)(4,2)(3,2)(3,4)(0,3)(1,2)
MATCH (a)-[*1..1]-(b) RETURN count(*)      pandas 36  polars 18
MATCH (a)-[:F*1..1]-(b) RETURN count(*)    pandas 16  polars 8

-[*1..2]- and -[*1..3]- agree, which is exactly why the existing tests miss it — only the degenerate k == 1 window is wrong.

3. Undirected -[*1..k]- over-counts with a filtered seed, or as a non-first segment

nodes 0..6 (kind alternating a/b)
edges (0,3)(6,3)(4,2)(1,3)(6,3)(3,1)(3,5)(4,1)(3,3)(0,3)
MATCH (a {kind:'a'})-[*1..2]-(b) RETURN count(*)   pandas 38   polars 122
MATCH (a)-[]->(b)-[*1..2]-(c) RETURN count(*)      pandas 316  polars 464

All directed equivalents agree, so this is specific to the undirected doubled-pair expansion when the segment does not start from the full node set.

Suggested handling

Until the multiplicity reconstruction is fixed, the honest move is the same one #1781 made for the unbounded case: decline these shapes rather than answer differently. Concretely — bounded directed min_hops >= 3, undirected min_hops == max_hops == 1, and undirected variable-length with a filtered/non-initial seed.

A differential fuzz harness against the pandas oracle over random small graphs (DAG + cyclic, parallel edges, self-loops) catches all three; worth adding to the polars engine suite, since each of these passed the existing tests.

Note when fuzzing: undirected unbounded shapes through the pandas oracle can blow up (75 GiB RSS observed). Guard with RLIMIT_AS + SIGALRM + a container memory cap.

🤖 Generated with Claude Code

https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions