// Semijoin reduction for constant-anchored chains (task 205 round 8): a // run of anonymous expand hops that leads from a pattern variable into a // node that is CONSTANT across the stage's input rows is a fixed unary // predicate on that variable -- (c)-[:LOC]->(:City)-[:PART]->(country) // with country identical on every seeded row tests nothing but c. The // chain's ops are absorbed: the qualifying node set is enumerated ONCE by // walking the chain backwards from the constant, and the variable's // binding level sweeps candidates against it at fill time, so the // per-row hop enumeration (and its row traffic) disappears. Fires on // generic structure -- an into-bound tail whose target the existing // batch-constant analysis proves invariant -- never on query identity.
package exec import ( ) // constChainAbsorbs counts absorbed chains -- the engagement oracle: a // qualifying chain must absorb exactly once per stage compile; 0 on the // qualifying shape means every row still walks the hops. var constChainAbsorbs int // disableConstChainAbsorb pins result identity in tests: the general // per-row enumeration must produce exactly the rows the absorbed form // does. var disableConstChainAbsorb bool // absorbedStage is compileStage's rewritten view of a stage whose // constant-anchored chains were absorbed. type absorbedStage struct { ops []plan.BindOp // profMap maps an exec-op index to its original index, so PROFILE // counts land on the right operator lines (absorbed ops report 0). profMap []int // gates test the chains' carried head variables, once per input row. gates []memberGate } // memberGate tests a CARRIED slot (bound upstream of this stage) against // an absorbed chain's qualifying set, once per input row. type memberGate struct { slot int set []uint32 } func ( *memberGate) ( []value.Value) bool { , := [.slot].AsNode() if ! { return false } , := slices.BinarySearch(.set, uint32()) return } // absorbConstChains detects and absorbs constant-anchored chains. Every // gate fails closed: // - the segment's projection must be DISTINCT (absorbing collapses the // chain's match multiplicity, which only a distinct boundary erases); // - the stage must not be OPTIONAL and must bind no named path; // - every chain op is a plain fixed expand with no named rel, no // uniqueness participation, and a fresh, unnamed, otherwise-unused // intermediate node; // - the tail is an into-bound expand whose target slot every seeded // input agrees on (uniformIn -- unlike constIn it tolerates the // rebind itself, which never changes a bound value); // - the variable under test is bound by an untracked level (its fill // sweeps run only there). func absorbConstChains( *eval.Ctx, *plan.MatchStage, map[string]int, func(int) bool, []value.Value, bool) *absorbedStage { if disableConstChainAbsorb || ! || .Optional || .PathBind != nil { return nil } := .Ops := map[int]bool{} for , := range { [] = true } // slotUses counts references to a slot across all ops, so an // intermediate consumed by anything outside its chain refuses. := map[int]int{} := func( int) { if >= 0 && != plan.NoSlot { []++ } } for := range { := &[] if .Kind == plan.OpScan { (.Slot) } else { (.From) (.To) (.RelSlot) } } := func( *plan.BindOp) bool { return .Kind == plan.OpExpand && .Uniq == nil && .RelSlot == plan.NoSlot } := map[int]bool{} var []memberGate for := len() - 1; >= 1; -- { := &[] if [] || !() || !.Rebind { continue } := .To if !() || >= len() { continue } , := [].AsNode() if ! { continue } // Extend the chain upward through fresh anonymous intermediates: // ops[i..j] with ops[k].From == ops[k-1].To. := for -1 >= 0 { := &[-1] := [].From if !() || .Rebind || .To != { break } // The intermediate must be invisible: unnamed and read by // exactly its two chain neighbors. if [] || [] != 2 { break } -- } := [].From // Only a CARRIED head variable absorbs: when this stage itself // binds v fresh, the planner saw the whole pattern and anchoring // from the constant was its call -- a per-row gate could not run // before v binds anyway. A ScanArg re-scan of v does NOT count: // it re-tests a value the row already carries (the gate and the // re-test are both filters, order-independent). := true for := 0; < ; ++ { := &[] if .Kind == plan.OpScan && .Source.Kind == plan.ScanArg { continue } if slotOf() == { = false } } if ! { continue } := chainMembers(, [:+1], ) for := ; <= ; ++ { [] = true } = append(, memberGate{slot: , set: }) constChainAbsorbs++ = // resume scanning above the chain } if len() == 0 { return nil } := &absorbedStage{gates: } for := range { if ![] { .ops = append(.ops, []) .profMap = append(.profMap, ) } } return } // chainMembers enumerates the nodes that can start the chain, walking it // backwards from the constant tail node: each step collects the reversed // neighbors over the op's rel types and applies the PREVIOUS op's target // matcher (the constraints the forward walk would have tested on that // intermediate). The result is sorted for binary-search membership. func chainMembers( *eval.Ctx, []plan.BindOp, graph.NodeID) []uint32 { := &[len()-1] := make([]graph.PropSpec, len(.Props)) for , := range .Props { [] = graph.PropSpec{Key: .Key, Val: eval.LitValue(, .Val)} } if !.G.NodeMatcherAccepts(.G.CompileNodeMatcher(.Labels, ), ) { return nil } := []graph.NodeID{} var []graph.NodeID for := len() - 1; >= 0; -- { := &[] := .G.CompileRelMatcher(.Types) var *graph.NodeMatcher if > 0 { := &[-1] := make([]graph.PropSpec, len(.Props)) for , := range .Props { [] = graph.PropSpec{Key: .Key, Val: eval.LitValue(, .Val)} } = .G.CompileNodeMatcher(.Labels, ) } = [:0] for , := range { = .G.AppendNeighborsMatched(, , flipDir(.Dir), ) } slices.Sort() = slices.Compact() if != nil { := 0 for , := range { if .G.NodeMatcherAccepts(, ) { [] = ++ } } = [:] } , = , } := make([]uint32, len()) for , := range { [] = uint32() } slices.Sort() return }