// 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 execimport ()// 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 ! {returnfalse } , := 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 {ifdisableConstChainAbsorb || ! || .Optional || .PathBind != nil {returnnil } := .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 []memberGatefor := len() - 1; >= 1; -- { := &[]if [] || !() || !.Rebind {continue } := .Toif !() || >= 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] := [].Fromif !() || .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). := truefor := 0; < ; ++ { := &[]if .Kind == plan.OpScan && .Source.Kind == plan.ScanArg {continue }ifslotOf() == { = false } }if ! {continue } := chainMembers(, [:+1], )for := ; <= ; ++ { [] = true } = append(, memberGate{slot: , set: })constChainAbsorbs++ = // resume scanning above the chain }iflen() == 0 {returnnil } := &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, ), ) {returnnil } := []graph.NodeID{}var []graph.NodeIDfor := len() - 1; >= 0; -- { := &[] := .G.CompileRelMatcher(.Types)var *graph.NodeMatcherif > 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 { := 0for , := range {if .G.NodeMatcherAccepts(, ) { [] = ++ } } = [:] } , = , } := make([]uint32, len())for , := range { [] = uint32() }slices.Sort()return}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.