// MATCH stage runner: compile the stage's matchers and WHERE-conjunct// pushdown buckets once, then walk each input row through the bind chain.
package execimport ()// stageComp is a MATCH stage's per-op pre-resolved state: node matchers,// rel matchers, and the WHERE conjuncts bucketed by pushdown level --// each level's bucket split into specialized per-candidate predicates// (levelPreds) and general row evaluations (levelFilters).type stageComp struct {// ops are the stage's EXEC ops -- stage.Ops unless a constant-anchored // chain was absorbed (semired.go); profMap maps exec indices back to // original op indices for PROFILE alignment (nil when identical). ops []plan.BindOp profMap []int// gates test carried-in slots against absorbed-chain membership sets // once per input row (semired.go). gates []memberGate matchers []*graph.NodeMatcher relMatchers []*graph.RelMatcher levelFilters [][]RowEval levelPreds [][]compile.CandPred levelBatch [][]compile.CandBatch hopGates []hopGate semijoins []semiCache// seedRel/seedNode are per-op per-chain per-hop matchers for // ScanExistsSeed walks, resolved once with the rest of the stage. seedRel [][][]*graph.RelMatcher seedNode [][][]*graph.NodeMatcher}// compileStage pre-resolves the stage's constant names once (labels,// property keys, rel types, params) so the per-candidate work is bitmap// contains + column reads. constIn reports segment-wide hoisting-constant// slots; sample is a seeded input row carrying their values.func compileStage( *eval.Ctx, *plan.MatchStage, map[string]int, , func(int) bool, []value.Value, bool) *stageComp { := .Opsvar []intvar []memberGateif := absorbConstChains(, , , , , ); != nil { , , = .ops, .profMap, .gates } , , := buildLevelFilters(, , , , , ) := &stageComp{ops: ,profMap: ,gates: ,matchers: make([]*graph.NodeMatcher, len()),relMatchers: make([]*graph.RelMatcher, len()),levelFilters: ,levelPreds: ,levelBatch: ,hopGates: buildHopGates(, ),semijoins: buildSemijoins(), } .seedRel = make([][][]*graph.RelMatcher, len()) .seedNode = make([][][]*graph.NodeMatcher, len())for := range { := &[] := make([]graph.PropSpec, len(.Props))for , := range .Props { [] = graph.PropSpec{Key: .Key, Val: eval.LitValue(, .Val)} } .matchers[] = .G.CompileNodeMatcher(.Labels, ) .relMatchers[] = .G.CompileRelMatcher(.Types)if .Kind == plan.OpScan && .Source.Kind == plan.ScanExistsSeed { .seedRel[] = make([][]*graph.RelMatcher, len(.Source.Seeds)) .seedNode[] = make([][]*graph.NodeMatcher, len(.Source.Seeds))for := range .Source.Seeds { := .Source.Seeds[].Hops .seedRel[][] = make([]*graph.RelMatcher, len()) .seedNode[][] = make([]*graph.NodeMatcher, len())for := range { .seedRel[][][] = .G.CompileRelMatcher([].Types) .seedNode[][][] = .G.CompileNodeMatcher([].Labels, nil) } } } }return}// buildLevelFilters splits the stage WHERE into top-level AND-conjuncts// and buckets each at the earliest op level where every slot it reads is// bound; graph-touching conjuncts keep last-level timing. A conjunct then// prunes a candidate the moment it can fail instead of after the whole// pattern binds. A conjunct whose only row dependency is its level's own// node slot and whose shape specializes lands in the batch buckets// (compile.CandidateBatch: one columnar pass over the whole candidate// buffer) or the preds buckets (compile.CandidatePred: per-candidate// closure) instead of the general row evaluation.func buildLevelFilters( *eval.Ctx, *plan.MatchStage, []plan.BindOp, map[string]int, func(int) bool, []value.Value) ([][]RowEval, [][]compile.CandPred, [][]compile.CandBatch) { := max(len(), 1) := make([][]RowEval, ) := make([][]compile.CandPred, ) := make([][]compile.CandBatch, )if .Where == nil {return , , }// A named-path bind assembles p only AFTER the walk, so its WHERE runs // as a post-path filter instead (a level filter over the still-null // path slot would silently drop every row).if .PathBind != nil {return , , } := make(map[int]int, len())for := range { := &[]if , := [slotOf()]; ! { [slotOf()] = }if := relSlotOf(); != plan.NoSlot {if , := []; ! { [] = } } }// A slot is batch-constant when the segment never binds it and the // seeded inputs agree on its value (constIn); a carried slot is // loop-invariant per match-call even when it varies across input rows. := func( int) bool { , := []; return } := func( int) bool { return !() && () } := func( int) bool { return !() }var []ast.Exprplan.SplitAnd(.Where, &) = rewriteDateEqRanges(, , )for , := range { := hoistEval(, compileEval(, , ), , , , )var []int , := false, falseevalPushdown(, , , &, &, &) := - 1if ! { = 0for , := range {if , := []; && > { = } }// A graph-walking conjunct (a memoized subquery: its memo bounds // only repeat cost -- every distinct correlation tuple still // pays a walk) placed at its binding level would run once per // row the NEXT ops are about to discard. Ops that can only // SHRINK the row set -- rebind expands, existence checks // emitting at most one row per input -- cannot change what the // conjunct is asked about, and a conjunction is // order-independent, so slide the walk forward over them and // stop at the first op that can grow the rows. The early-prune // placement is preserved everywhere it is a win (any op after // the walk that fans out).if {for +1 < { := &[+1]if .Kind == plan.OpExpand && .Rebind { ++continue }break } } } := min(, -1)if , := .(*compile.Compiled); && < len() {// A uniqueness-tracked level never fill-sweeps, so its // conjuncts stay per-candidate (CandidatePred below).if [].Uniq == nil {if , := compile.CandidateBatch(, slotOf(&[])); { [] = append([], )continue } }if , := compile.CandidatePred(, slotOf(&[]), ); { [] = append([], )continue } } [] = append([], ) }return , , }// slotAgrees reports whether slot holds an identical value on every row of// the batch (vacuously true for an empty or single-row batch) -- the one// "is this slot batch-constant" test, with an explicit out-of-range// policy. padNull treats a position beyond a row's width as Null (the// seeded-input convention, where narrow inputs pad with nulls); without it// any out-of-range read across differing rows disqualifies, guarding// callers that will index rows at the slot directly.func slotAgrees( int, [][]value.Value, bool) bool {iflen() == 0 {returntrue }varvalue.Valueif < len([0]) { = [0][] } elseif ! && len() > 1 {returnfalse }for , := range [1:] {varvalue.Valueif < len() { = [] } elseif ! {returnfalse }if !value.Identical(, ) {returnfalse } }returntrue}
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.