Bug: a grouped count/avg over a variable-length pattern with a relationship predicate ignores the predicate since #1080
Maintainers usually reply within 1 day
A pull request for this has already been merged.
- #1133 by @adsharma — merged
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 74/100
Research direction
Start at tryRewriteGroupedReachableCount, where the gate already rejects recursiveExtend->hasNodePredicate(); check RecursiveInfo::relPredicate and reject the rewrite when it is set, alongside the self-loop and negative-shape guards the issue mentions. Locate the C++ optimizer source (the gate lives in the query planner/optimizer code) and the existing tests in OptimizerTest.GroupedReachableCount, adding a case with (r, n | WHERE r.w > 2). Done means the Python repro from the issue returns [(True, 6, 2.0)], EXPLAIN no longer shows GROUPED_REACHABLE_COUNT for the predicate query, and the new optimizer test passes.
Written by the indexing model from the issue text.
Description
Since #1080 the grouped count and avg rewrite (GROUPED_REACHABLE_COUNT) is also applied to a variable-length pattern that has a relationship predicate, and it ignores the predicate. The same query returns correct rows without the aggregate and with the aggregate before #1080.
Builds, both from source (Release, make python equivalent, g++ 15.2, Python 3.12.13, Ubuntu 26.04): the merge of #1080 at 99ae4efe9578226f48396e0ecfe6da5ffca18b30, against its base 95dbf5ed180f625993a49a64b92f4d968401e90e. The published 0.21.2 wheel behaves like the base.
Repro, self-contained (three nodes, edges 0 to 1 with w=1, 1 to 2 with w=5, 2 to 2 with w=5):
import ladybug
db = ladybug.Database("db.lbug")
c = ladybug.Connection(db)
c.execute("CREATE NODE TABLE N(id INT64 PRIMARY KEY, active BOOLEAN)")
c.execute("CREATE REL TABLE E(FROM N TO N, w INT64)")
for i, a in [(0, True), (1, False), (2, True)]:
c.execute(f"CREATE (:N {{id: {i}, active: {str(a).lower()}}})")
for a, b, w in [(0, 1, 1), (1, 2, 5), (2, 2, 5)]:
c.execute(f"MATCH (x:N {{id: {a}}}), (y:N {{id: {b}}}) CREATE (x)-[:E {{w: {w}}}]->(y)")
q = "MATCH (a:N)-[r:E*1..3 (r, n | WHERE r.w > 2)]->(b:N) RETURN b.active, count(b), avg(b.id)"
print(sorted(map(tuple, c.execute(q).get_all()), key=str))
Only the edges with w > 2 may be used, so the walks are 1 to 2, 2 to 2, and 2 to 2 to 2 repeated up to three hops, all ending on active = true. Expected: [(True, 6, 2.0)].
At the base commit and on 0.21.2: [(True, 6, 2.0)]. At 99ae4efe95: [(False, 1, 1.0), (True, 8, 2.0)], which is exactly the answer of the same pattern with no predicate (MATCH (a:N)-[r:E*1..3]->(b:N) ...). Row by row, RETURN a.id, b.id, count(*) on the same predicate pattern is still right at 99ae4efe95 ([(1, 2, 3), (2, 2, 3)]), so only the rewritten aggregate is wrong.
EXPLAIN at 99ae4efe95 shows GROUPED_REACHABLE_COUNT for the predicate query. The gate in tryRewriteGroupedReachableCount rejects recursiveExtend->hasNodePredicate(), which is !children.empty(), but I could not find anything that rejects a relationship predicate (RecursiveInfo::relPredicate), so the walk counts are taken over the full table.
How it was found: a differential run against a brute-force walk enumeration, 120 random graphs with self-loops, parallel edges and cycles, three hop ranges (*0..3, *1..3, *2..4), shape RETURN b.active, count(b), avg(b.score) with (r, n | WHERE r.w > 2). At the base commit every mismatch is at *0..3 and is the known length-0 duplication that #1080 fixes. At 99ae4efe95 356 of the 360 cells for this shape differ from the enumeration, in all three ranges. The other shapes in the matrix (count, count DISTINCT, avg, min, max, sums, one and two group keys, a WHERE on the endpoint or the start, trail, backward, undirected) have no mismatch at 99ae4efe95.
Suggested fix: reject the rewrite when the recursive info carries a relationship predicate, and add that case next to the self-loop and negative shapes in OptimizerTest.GroupedReachableCount.
- Dominant language
- C++
- Stars
- 1.8k
- Forks
- 148
- Avg merge
- 14h 57m
- Merged PRs (30d)
- 124
Getting set up
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from LadybugDB/ladybug
-
Bug: SET p.prop = NULL ... RETURN p.prop returns the old value for every row except the firstPossibly taken @Tyagiquamar claimed this 1 day ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
Planner follow-ups from #1125 review: null outerAccumulate segfault under enable_plan_optimizer=false, no cost guard for the correlated-PK chain, and late filter-scope errorsPossibly taken @Tyagiquamar claimed this today. Openbug
Difficulty 5/5 Over a week Newbie friendliness 25/100
Maintainers usually reply within 1 day
-
Bug: is_sorted assertion in scanCommittedInMem after a failed checkpoint with deleted in-memory relsOpen
Difficulty 4/5 3-5 days Newbie friendliness 48/100
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
Maintainers usually reply within 1 day
-
bug
Difficulty 4/5 3-5 days Newbie friendliness 58/100
LadybugDB/ladybug#1117 · 1 comment ·
Maintainers usually reply within 1 day
All issues in LadybugDB/ladybug
Similar issues
-
Status: Awaiting triage
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
espressif/arduino-esp32#12984 ·
Maintainers usually reply within 1 day
-
torch_ops/logprob.cu does not compile with the serving container's nvcc (13.3.73); check_torch_ops.py cannot run as shippedPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 Under an hour Newbie friendliness 72/100
ashhart/TensorFold#535 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day