// Early shortest-path row gating: when a segment's whole job is one // non-expanding ANY SHORTEST stage and the following boundary chain is a // pure per-row LET/FILTER pipeline, the path search plus every // gate-evaluable filter conjunct also runs INSIDE the previous segment, // inserted before its remaining expansion stages -- so rows the chain // would kill never pay the expansion (the BFS-first order a hand-written // kernel uses). The injection is a semijoin-style copy, not a move: the // downstream stages run unchanged, which keeps the transform sound by // construction -- the gate only removes rows a later deterministic // per-row filter would remove anyway (the path search is a pure function // of its endpoint pair and the graph). Bindings hide past the segment's // projected names, so nothing else can observe them.
package plan import // injectSPGates runs the gating pass over a branch's built segments. func injectSPGates( []*Segment) { for := 0; +1 < len(); ++ { injectSPGate(, ) } } // injectSPGate considers hoisting segment i+1's shortest-path stage (and // the pure filter chain behind it) into segment i as a GateStage. Every // bail-out leaves the plan untouched. func injectSPGate( []*Segment, int) { , := [], [+1] // The gated segment must be exactly one required single-path stage: an // ALL form expands rows and an OPTIONAL one keeps them, so neither can // act as a filter. if len(.Stages) != 1 { return } , := .Stages[0].(*SpStage) if ! || .All || .Optional { return } // The host's boundary must be a pure per-row projection: filtering // before it commutes only when it neither aggregates, dedups, orders, // paginates, nor post-computes. if !pureRowProj(&.Proj) { return } // Resolve the stage's slots to names through its own segment. := make(map[int]string, len(.Slots)) for , := range .Slots { [] = } , , := [.From], [.To], [.PathSlot] if == "" || == "" || == "" { return } // A referenced column is gate-evaluable when the host projects it as // itself (a bare variable under its own name): then the host's slot // holds exactly the value the chain saw. := map[string]bool{} for , := range .Proj.Returns { if isVar(.Expr, .Name) { if , := .Slots[.Name]; { [.Name] = true } } } if ![] || ![] { return } if , := .Slots[]; { return } [] = true // Walk the boundary chain from the gated segment forward while it // stays a pure per-row pipeline, gathering evaluable LET columns and // filter conjuncts. Conjuncts are copied, never consumed -- the // downstream filter re-running on survivors is idempotent. := func() []string { := make([]string, 0, len()) for := range { = append(, ) } return } type struct { string ast.Expr } var [] var []ast.Expr := map[string]bool{: true, : true} for := + 1; < len(); ++ { := [] if > +1 && len(.Stages) != 0 { break } if !pureRowProj(&.Proj) { break } := true for , := range .Proj.Returns { if isVar(.Expr, .Name) { continue } if , := .Slots[.Name]; || [.Name] { = false continue } if len(freeVarsOutside(.Expr, ())) != 0 { continue } = append(, {: .Name, : .Expr}) [.Name] = true } if ! { break } if .PostWhere != nil { var []ast.Expr SplitAnd(.PostWhere, &) for , := range { if len(freeVarsOutside(, ())) == 0 { = append(, ) } } } } if len() == 0 { return } for , := range { collectAllVars(., ) } for , := range { collectAllVars(, ) } // Insertion point: the earliest stage boundary where every host slot // the gate reads is bound, that does not split a relationship- // uniqueness scope, and that leaves at least one stage after it (a // gate just before the projection saves nothing). := gateInsertPos(, , ) if < 0 || >= len(.Stages) { return } // Materialize: hidden slots for the path and each hoisted LET, the // names registered so the copied expressions resolve against the // host's own slot map. := &GateStage{Sp: *, Where: rebuildAnd()} := .RowWidth .Slots[] = .Sp.PathSlot = .Sp.From = .Slots[] .Sp.To = .Slots[] ++ for , := range { .Slots[.] = .Derived = append(.Derived, GateDerived{Slot: , Expr: .}) ++ } .RowWidth = .Stages = append(.Stages[:], append([]Stage{}, .Stages[:]...)...) } // pureRowProj reports whether a projection is per-row passthrough shaped: // no aggregation, DISTINCT, ordering, pagination, or post-aggregate // computation. Filtering its input commutes with it. func pureRowProj( *ProjPlan) bool { return !.Aggregated && !.Distinct && len(.OrderBy) == 0 && .Skip == nil && .Limit == nil && len(.Post) == 0 && .NHidden == 0 } // gateInsertPos is the earliest stage boundary of a where every slot named // by need (that a's slot map knows and the host can evaluate) is bound -- // by the segment's inputs or a preceding stage -- without splitting a // relationship-uniqueness scope across the boundary; -1 when none. func gateInsertPos( *Segment, , map[string]bool) int { := map[int]bool{} for := range { if ![] { continue } if , := .Slots[]; { [] = true } } := make([][]int, len(.Stages)) := map[int]bool{} for , := range .Stages { stageSlotBinds(, func( int) { [] = append([], ) [] = true }) } // Slots the segment's stages never bind are inputs: bound before // stage 0. := map[int]bool{} for , := range .Slots { if ![] { [] = true } } := func() bool { for := range { if ![] { return false } } return true } for := 0; <= len(.Stages); ++ { if () && !splitsUniqScope(.Stages, ) { return } if < len(.Stages) { for , := range [] { [] = true } } } return -1 } // stageSlotBinds reports every slot a stage binds. func stageSlotBinds( Stage, func(int)) { switch s := .(type) { case *MatchStage: for := range .Ops { := &.Ops[] if .Kind == OpScan { (.Slot) } else { (.To) } if .Kind == OpExpand || .Kind == OpVarExpand { (.RelSlot) } } if .PathBind != nil { (.PathBind.PathSlot) } case *HashJoinStage: for , := range .PayloadSlots { () } (.KeySlot) case *SpStage: (.PathSlot) case *GateStage: (.Sp.PathSlot) for , := range .Derived { (.Slot) } case *CallStage: (.NodeSlot) (.ValueSlot) (.DepthSlot) case *UnwindStage: (.OutSlot) case *CallSubqueryStage: for , := range .OutSlots { () } } } // splitsUniqScope reports whether inserting a batch barrier at pos would // separate two stages sharing a relationship-uniqueness scope: the // barrier buffers rows, so used-relationship pairs pushed by the earlier // stage would already be popped when the later stage checks them. func splitsUniqScope( []Stage, int) bool { := map[uint32]bool{} for , := range [:] { stageUniqScopes(, func( uint32) { [] = true }) } := false for , := range [:] { stageUniqScopes(, func( uint32) { if [] { = true } }) } return } // stageUniqScopes reports the relationship-uniqueness scopes a stage // participates in. func stageUniqScopes( Stage, func(uint32)) { switch s := .(type) { case *MatchStage: (.Scope) case *HashJoinStage: for , := range .Build { (.Scope) } if .Probe.Uniq != nil { (.Probe.Uniq.Scope) } } }