// Anchor scan-source selection and pruning analysis for match-stage// lowering: choosing an anchor's scan source (id seek, text-index scan,// EXISTS-seed candidate scan, or the base label/property scan), deriving// the backward seed walks from a correlated EXISTS, and flagging bounded// var-expands whose duplicate endpoints can collapse under a// multiplicity-insensitive projection. Split from build.go, which holds// the segment and match-stage lowering.
package planimport ()// startScanSource picks the anchor's scan source: an id seek from a WHERE// conjunct, a per-row id-variable seek, a substring-index candidate scan// (labelled start, no inline anchor property), else the label/property/all// scan.func startScanSource( *ast.Pattern, ast.Expr, map[string]int, map[int]bool, graph.Graph) ScanSource { := .Start.Varif != "" {if := idSeekLiteral(, ); != nil {returnScanSource{Kind: ScanNodeID, Value: *} }if := idSeekVar(, , , ); != NoSlot {returnScanSource{Kind: ScanNodeIDVar, Slot: } }// A value seek -- an inline prop OR a WHERE equality (bestPropSeek sees // both) -- outranks the substring / EXISTS-seed fallbacks; only reach // for those when nothing seeks by value.if , := bestPropSeek(&.Start, , ); ! {iflen(.Start.Labels) > 0 {if := textMatchSeek(, ); != nil {returnScanSource{Kind: ScanTextMatch, Label: .Start.Labels[0], Field: .field, Mode: .mode, Value: .needle} } }// An EXISTS conjunct correlated to sv and anchored at a bound // variable narrows the scan to its candidate superset; the kept // conjunct finalizes. Only worth it against a broad base source.if := existsSeedChains(, , , ); != nil { := scanSource(&.Start, , ) .Kind = ScanExistsSeed .Seeds = return } } }returnscanSource(&.Start, , )}// existsSeedChains recognizes a WHERE conjunct that is EXISTS { pattern }// (or an OR of such EXISTSes, all of which must qualify) where the// pattern runs between sv at one end and a bound variable at the other// over fixed single hops. It returns one backward walk per disjunct// (anchor -> sv, directions flipped when the anchor is the pattern's// end), or nil when no conjunct qualifies.func existsSeedChains( ast.Expr, string, map[string]int, map[int]bool) []SeedChain {if == nil {returnnil }var []ast.ExprSplitAnd(, &)for , := range {if := seedDisjuncts(, , , ); != nil {return } }returnnil}// seedDisjuncts qualifies one conjunct: a single EXISTS, or an OR tree// whose every leaf is a qualifying EXISTS (a non-qualifying leaf voids// the whole disjunction -- its rows would be missed).func seedDisjuncts( ast.Expr, string, map[string]int, map[int]bool) []SeedChain {switch n := .(type) {case *ast.Binary:if .Op != ast.OpOr {returnnil } := (.LHS, , , )if == nil {returnnil } := (.RHS, , , )if == nil {returnnil }returnappend(, ...)case *ast.Exists:if := seedChainOf(.Pattern, , , ); != nil {return []SeedChain{*} } }returnnil}// seedChainOf builds the anchor->sv walk for one EXISTS pattern, or nil// when the shape doesn't qualify (sv not at an end, no bound anchor at// the other end, or a quantified hop).func seedChainOf( *ast.Pattern, string, map[string]int, map[int]bool) *SeedChain {if == nil || len(.Hops) == 0 {returnnil }for , := range .Hops {if .Rel.Length != nil {returnnil } } := .Hops[len(.Hops)-1].Node.Var := func( string) (int, bool) {if == "" || == {return0, false } , := []return , && [] }if .Start.Var == {if , := (); {// Walk end -> start: hops reversed, directions flipped; each // step lands on the PRIOR hop's node (finally the start). := SeedChain{AnchorSlot: }for := len(.Hops) - 1; >= 0; -- { := &.Startif > 0 { = &.Hops[-1].Node } .Hops = append(.Hops, SeedHop{Dir: mapDir(.Hops[].Rel.Dir).Reverse(),Types: .Hops[].Rel.Types,Labels: .Labels, }) }return & }returnnil }if == {if , := (.Start.Var); {// Walk start -> end in pattern order. := SeedChain{AnchorSlot: }for := range .Hops { .Hops = append(.Hops, SeedHop{Dir: mapDir(.Hops[].Rel.Dir),Types: .Hops[].Rel.Types,Labels: .Hops[].Node.Labels, }) }return & } }returnnil}// flagDedupEndpoints marks bounded var-expands whose relationships/path// are unused so each distinct endpoint binds once under a DISTINCT// projection (duplicate rows would collapse anyway). Conservative: only// ops with no rel slot in a stage with no path bind, where no projected// expression or ORDER BY key references a rel variable (none can -- the// rel slot is unbound), qualify.// projCannotSeeDuplicateRows reports whether the projection's output is// invariant under dropping an EXACT-duplicate row (every column identical) --// the precondition for collapsing a var-expand's per-trail rows to one row per// endpoint. True for a plain DISTINCT, and for a grouped aggregation whose// every aggregate is multiplicity-insensitive: a duplicate row lands in the// same group (its keys are the same row) and contributes nothing new. Nested// aggregate wrappers (Post) are declined conservatively -- the direct-aggregate// case (e.g. LDBC IC6's count(DISTINCT ...)) is the one that matters.func projCannotSeeDuplicateRows( *ProjPlan) bool {if .Distinct && !.Aggregated {returntrue }if !.Aggregated || len(.Aggs) == 0 || len(.Post) > 0 {returnfalse }for := range .Aggs {if !aggIgnoresRowMultiplicity(&.Aggs[]) {returnfalse } }returntrue}// aggIgnoresRowMultiplicity reports whether an aggregate's result is unchanged// by dropping an exact-duplicate row. min/max are idempotent under duplicates;// a DISTINCT count/sum/avg already ignores value multiplicity, so a same-value// duplicate contributes nothing. count(*) / count(x) / sum / avg without// DISTINCT grow with each duplicate, and collect is order-bearing even with// DISTINCT (the list carries encounter order, which the collapse changes) --// none of those are eligible.func aggIgnoresRowMultiplicity( *AggCol) bool {switch .Kind {caseAggMin, AggMax:returntruecaseAggCollect:returnfalsedefault: // AggCount, AggSum, AggAvgreturn .Distinct }}func flagDedupEndpoints( []Stage, *ProjPlan, map[string]int) { _ = _ = for , := range { , := .(*MatchStage)if ! || .PathBind != nil {continue }for := range .Ops { := &.Ops[]if .Kind == OpVarExpand && .Max != nil && .RelSlot == NoSlot && .RelPred == nil { .DedupEndpoints = true } } }}
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.