Support directional chain hops in anti-edge count rewrite - #921
Merged
Merged
Conversation
adsharma
force-pushed
the
anti-edge-count-directional
branch
2 times, most recently
from
September 5, 2026 23:02
28c7544 to
335b4f5
Compare
CountAntiEdgeChain previously required the two triangle chain hops to be BOTH-direction extends, so LSQB q9 (as written in the benchmark, with directed knows edges) never fired the rewrite and fell back to a ~2.2s hash join plan. - Generalize the T - S - A count arithmetic to any FWD/BWD/BOTH combination of chain hop directions and anti-edge match direction. Chain enumeration is row-level (a BOTH hop enumerates FWD rows then BWD rows, no dedup), the anti-edge mark is boolean per key pair so row partners are deduped when subtracting A. - Record the hop/anti-edge directions in LogicalCountAntiEdgeChain and plumb them through the plan mapper into the physical operator. - Fix the mid-node resolution in the optimizer: scanNode->getNodeID() is the internal-ID property (uniqueName has the ._ID suffix), so the mid NodeExpression is now recovered from the chain extends and names are compared as plain variable names (same fix in the suffix scan check). - Fix build errors (const Transaction*, containsOp declaration) and warnings (dangling reference, unused variable). - Remove the LBUG_TRACE_ANTI trace scaffolding. Validation on the LSQB sf-like database: q9.cypher now fires the operator and returns the same count as the join plan (268837983); execution drops from ~2219ms to ~23ms (~95x). Six direction combos were cross-checked against the join-plan ground truth.
Move the logical plan tree dumper out of count_rel_table_optimizer.cpp into Optimizer::optimize, which every query goes through. With LBUG_DUMP_LOGICAL set in the environment, the logical plan is printed to stderr as a readable indented tree (one operator per line, with extend directions, join keys/types, filter predicates, aggregate keys and scan targets) both before and after optimization.
adsharma
force-pushed
the
anti-edge-count-directional
branch
from
September 6, 2026 01:40
335b4f5 to
23a2b3e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Directional chain support. The anti-edge count rewrite previously required both triangle chain hops to be
BOTH-direction extends, so LSQB q9 as written in the benchmark ((p1)-[:KNOWS]->(p2)-[:KNOWS]->(p3)with a directed anti-edge) never fired and ran a ~2.2s hash join plan. TheT - S - Acount arithmetic is now parameterized by the enumeration direction of each chain hop and of the anti-edge match (any FWD/BWD/BOTH combination):T= all chain tuples, weighted by per-node suffix path countsDS=n0 = n2tuples (excluded byid(n0) <> id(n3))A= tuples where the anti-edge row exists, with row partners deduped per key pair (the mark is boolean)Chain enumeration is row-level (a
BOTHhop enumerates FWD rows then BWD rows, no dedup) to match the extend operator's semantics. Directions are recorded inLogicalCountAntiEdgeChainand plumbed through the plan mapper into the physical operator. Also fixes two latent bugs in the shape matcher (scanNode->getNodeID()is the internal-ID property, so._ID-suffixed unique names never matched plain node variable names) plus build errors/warnings, and removes theLBUG_TRACE_ANTItrace scaffolding.LBUG_DUMP_LOGICAL. The logical plan tree dumper is moved out of
count_rel_table_optimizer.cppintoOptimizer::optimize, which every query goes through. WithLBUG_DUMP_LOGICAL=1the logical plan prints to stderr as a readable indented tree (one operator per line: extend directions, join keys/types, filter predicates, aggregate keys, scan targets) both before and after optimization — much easier to read than EXPLAIN's ASCII-art boxes.Validation
q9.cypher: fires the operator, returns the same count as the join plan (268837983); execution ~2219ms → ~23ms (~95x).