// Monotonic var-length predicate pushdown (port of the Rust plan/mono.rs):// one normalizer answers "is this conjunct equivalent to sorted(L) /// reverse-sorted(L)?" over a canonicalized consecutive-pair core, and one// pass over the fully built segments lifts each recognized conjunct onto// its bounded var-expand as a MonoHopSpec so non-monotonic hops are pruned// during the walk. Mis-recognition is impossible, only missed// optimization: an unconsumed conjunct stays a correct post-hoc filter.
package planimport// monoListRef identifies the list a sortedness conjunct constrains: a// projected list alias (derived form), or rels(relVar) with the compared// property key (inline form).type monoListRef struct { alias string relVar string key string}// monoShape is the normalizer's result: which list must be sorted, in// which direction, and the source shape's null semantics (NullsPass on// MonoHopSpec: an incomparable pair prunes the all() family and passes// the violation-count family).type monoShape struct { ref monoListRef ascending bool nullsPass bool}// pushMonoPreds is the single monotonic-pushdown pass, run once per UNION// branch (and per CALL subquery body) over its fully built segments. An// inline conjunct in a MATCH stage's WHERE pushes onto that same stage's// var-expand; an alias conjunct in a segment's boundary WHERE resolves its// alias through the defining projection -- the segment's own or, via an// unbroken passthrough, an earlier segment's -- and pushes onto the// originating bounded var-expand.//// Every push CONSUMES the conjunct, which is safe because the walk prunes// with the exact filter semantics: hop pairs compare via the same// three-valued value.Compare the filter's </> uses, an incomparable pair// (missing key, NaN, mixed kinds) prunes for the all() shape and passes// for the violation-count shape (NullsPass), and a path's first hop// carries no comparison -- so the emitted set equals the filtered set and// re-checking removes nothing. Min-0 quantifiers never get a spec (the// reachability BFS has no walk order), keeping their conjunct as the post// filter. TestCrossSegmentMonoDropCorrectness pins the equivalence against// the plain filter semantics, including sparse and float keys.func pushMonoPreds( []*Segment) {for , := range {for , := range .Stages { , := .(*MatchStage)if ! || .Where == nil {continue } .Where = consumeMonoConjuncts(.Where, func( monoShape) bool {// Inline form, same stage only: a stage WHERE is scoped // inside its own (possibly OPTIONAL) match, so pruning this // stage's walk is exactly the WHERE's effect -- but pushing // into another stage's op would filter rows an OPTIONAL // would have null-extended.if .ref.relVar == "" {returnfalse }returnapplyMonoTarget([]Stage{}, .ref.relVar, NoSlot, .ref.key, .ascending, .nullsPass) }) }if .PostWhere != nil { .PostWhere = consumeMonoConjuncts(.PostWhere, func( monoShape) bool {if .ref.alias == "" {returnfalse }returnpushMonoAlias(, , ) }) } }}// consumeMonoConjuncts hands each recognized sortedness conjunct of a// WHERE's top-level AND chain to push, dropping the consumed ones and// rebuilding the rest.func consumeMonoConjuncts( ast.Expr, func(monoShape) bool) ast.Expr {var []ast.ExprSplitAnd(, &)var []ast.Exprfor , := range {if , := monoConjunctShape(); && () {continue } = append(, ) }returnrebuildAnd()}// pushMonoAlias resolves an alias-form conjunct seen at segment fi's// boundary WHERE: walk from fi's own projection back through earlier// segments to the one defining the alias as a rels-comprehension, then// push onto its bounded var-expand. The alias must pass through unchanged// from its definition to the filter -- fi's own projection included -- so// a projection that rebinds it to any other expression stops the search// (the filter constrains the rebound value, not the original walk).func pushMonoAlias( []*Segment, int, monoShape) bool {for := ; >= 0; -- { , := aliasReturnExpr(&[].Proj, .ref.alias)if ! {returnfalse }if , , := derivedListSourceExpr(); { , , := resolveMonoTarget([].Stages, [].Slots, )if ! {returnfalse }returnapplyMonoTarget([].Stages, , , , .ascending, .nullsPass) }if !isVar(, .ref.alias) {returnfalse } }returnfalse}// monoConjunctShape is the normalizer: both surface families -- the all()// quantifier over consecutive pairs and the violation-count form// size([.. WHERE a pair violates the order]) = 0 -- reduce to one// consecutive-pair core, so an unseen equivalent phrasing (shifted index// offsets, either operand order, a widened violation range, the inline// rels(e)[i].k elements or a bare list alias) lands on the same spec.func monoConjunctShape( ast.Expr) (monoShape, bool) {if , := .(*ast.ListPred); && .Quant == ast.QuantAll {returnmonoPairCore(.Var, .List, .Pred, false) } , := .(*ast.Binary)if ! || .Op != ast.OpEq {returnmonoShape{}, false }varast.Exprswitch {caseisZero(.RHS) && !isZero(.LHS): = .LHScaseisZero(.LHS) && !isZero(.RHS): = .RHSdefault:returnmonoShape{}, false } , := sizeArg().(*ast.ListComp)if ! || .Filter == nil || .Map != nil {returnmonoShape{}, false }returnmonoPairCore(.Var, .List, .Filter, true)}// monoPairCore recognizes the shared consecutive-pair core: ivar iterates// range(lo, size(REF)+m) (inclusive bounds) and pred compares REF elements// at affine offsets ivar+c and ivar+c+1. The covered pair set must start// exactly at pair 0 (a negative index would wrap to the list's end). For// the strict all() family (op < or >) it must also end exactly at pair// size-2 -- an out-of-range read is null, which fails all() -- and the// order is the operator's. For the violation family the inner comparison// is the VIOLATION (non-strict: >= violates ascending, <= descending; a// strict violation would mean a non-strict order the spec can't express)// and a longer range is fine (an out-of-range read is null, never a// violation), but a shorter one misses pairs.func monoPairCore( string, , ast.Expr, bool) (monoShape, bool) { , , , := monoRange()if ! {returnmonoShape{}, false } , := .(*ast.Binary)if ! {returnmonoShape{}, false } , , := monoElem(.LHS, ) , , := monoElem(.RHS, )if ! || ! || != || !sameMonoList(, ) {returnmonoShape{}, false } := - if != 1 && != -1 {returnmonoShape{}, false } := < varboolif {switch .Op {caseast.OpGte: = // violation: earlier >= latercaseast.OpLte: = !default:returnmonoShape{}, false } } else {switch .Op {caseast.OpLt: = // order: earlier < latercaseast.OpGt: = !default:returnmonoShape{}, false } } := min(, )if + != 0 {returnmonoShape{}, false }if := + ; ( && < -2) || (! && != -2) {returnmonoShape{}, false }returnmonoShape{ref: , ascending: , nullsPass: }, true}// monoElem matches one comparison side as an element read at an affine// offset of the iteration variable: L[ivar+c] (bare list alias) or// rels(e)[ivar+c].k (inline form).func monoElem( ast.Expr, string) (monoListRef, int64, bool) { := ""if , := .(*ast.PropOf); { , = .Base, .Key } , := .(*ast.Index)if ! {returnmonoListRef{}, 0, false } , := affineOffset(.Idx, )if ! {returnmonoListRef{}, 0, false }if != "" {if := relsArg(.Base); != "" {returnmonoListRef{relVar: , key: }, , true }returnmonoListRef{}, 0, false }if := asVarName(.Base); != "" {returnmonoListRef{alias: }, , true }returnmonoListRef{}, 0, false}// sameMonoList reports whether the range's size argument names the same// list the compared elements read (the alias, or rels(relVar)).func sameMonoList( ast.Expr, monoListRef) bool {if .alias != "" {returnisVar(, .alias) }returnrelsArg() == .relVar}// monoRange matches the two-argument range(<int lo>, size(<X>)+<m>) (a// step argument could skip pairs), returning lo, the size argument X, m.func monoRange( ast.Expr) (int64, ast.Expr, int64, bool) { , := .(*ast.Func)if ! || .Star || len(.Args) != 2 || !eqFold(.Name, "range") {return0, nil, 0, false } , := intLitVal(.Args[0])if ! {return0, nil, 0, false } , , := sizePlus(.Args[1])if ! {return0, nil, 0, false }return , , , true}// sizePlus matches size(<X>), size(<X>)+c, c+size(<X>), or size(<X>)-c,// returning X and the signed offset.func sizePlus( ast.Expr) (ast.Expr, int64, bool) {if := sizeArg(); != nil {return , 0, true } , := .(*ast.Binary)if ! {returnnil, 0, false }switch .Op {caseast.OpAdd:if := sizeArg(.LHS); != nil {if , := intLitVal(.RHS); {return , , true } }if := sizeArg(.RHS); != nil {if , := intLitVal(.LHS); {return , , true } }caseast.OpSub:if := sizeArg(.LHS); != nil {if , := intLitVal(.RHS); {return , -, true } } }returnnil, 0, false}// affineOffset matches <ivar>, <ivar>+c, c+<ivar>, or <ivar>-c, returning// the signed offset relative to the iteration variable.func affineOffset( ast.Expr, string) (int64, bool) {ifisVar(, ) {return0, true } , := .(*ast.Binary)if ! {return0, false }switch .Op {caseast.OpAdd:ifisVar(.LHS, ) {if , := intLitVal(.RHS); {return , true } }ifisVar(.RHS, ) {if , := intLitVal(.LHS); {return , true } }caseast.OpSub:ifisVar(.LHS, ) {if , := intLitVal(.RHS); {return -, true } } }return0, false}// sizeArg matches size(<x>) returning <x>.func sizeArg( ast.Expr) ast.Expr { , := .(*ast.Func)if ! || .Star || len(.Args) != 1 || !eqFold(.Name, "size") {returnnil }return .Args[0]}// derivedListSourceExpr matches a [r IN rels(src) | r.key] comprehension// (no filter -- a filter changes element positions; src may also be a bare// rel-list variable) to its rels source and property key.func derivedListSourceExpr( ast.Expr) (string, string, bool) { , := .(*ast.ListComp)if ! || .Filter != nil || .Map == nil {return"", "", false } := relsArg(.List)if == "" { = asVarName(.List) }if == "" {return"", "", false }switch m := .Map.(type) {case *ast.Prop:if .Var == .Var {return , .Key, true }case *ast.PropOf:ifisVar(.Base, .Var) {return , .Key, true } }return"", "", false}// aliasReturnExpr returns the projection expression bound to alias.func aliasReturnExpr( *ProjPlan, string) (ast.Expr, bool) {for := range .Returns {if .Returns[].Name == {return .Returns[].Expr, true } }returnnil, false}// resolveMonoTarget resolves rels(src) to the exact bounded var-expand to// push onto: src is a rel variable, or a named path whose single// var-length hop is that op. Ambiguity bails, and so does an OPTIONAL// stage: the boundary WHERE this path serves sits OUTSIDE the optional// match, so pruning its walk would null-extend rows the filter drops (the// constraint stays a correct post-hoc filter). Returns (relVar, relSlot,// ok) -- one of the two identifies the target.func resolveMonoTarget( []Stage, map[string]int, string) (string, int, bool) {for , := range { , := .(*MatchStage)if ! {continue }for := range .Ops { := &.Ops[]if .Kind == OpVarExpand && .RelVar == && .Max != nil {if .Optional {return"", NoSlot, false }return , NoSlot, true } } } , := []if ! {return"", NoSlot, false }for , := range { , := .(*MatchStage)if ! || .PathBind == nil || .PathBind.PathSlot != {continue }if .Optional {return"", NoSlot, false }// rels(p) spans the whole path; only a single var-length hop // equals one op's rels.var []*BindOpfor := range .Ops {if .Ops[].Kind == OpExpand || .Ops[].Kind == OpVarExpand { = append(, &.Ops[]) } }iflen() == 1 && [0].Kind == OpVarExpand && [0].Max != nil && [0].RelSlot == .PathBind.RelsSlot {return"", .PathBind.RelsSlot, true }return"", NoSlot, false }return"", NoSlot, false}// applyMonoTarget sets MonoHop on the identified bounded var-expand (when// it has no spec yet), reporting whether it attached. A min-0 quantifier// never gets a spec: exec resolves it via the reachability BFS, which has// no walk order to prune, so the caller must keep the conjunct.func applyMonoTarget( []Stage, string, int, string, , bool) bool {for , := range { , := .(*MatchStage)if ! {continue }for := range .Ops { := &.Ops[]if .Kind != OpVarExpand {continue } := ( != "" && .RelVar == ) || ( == "" && != NoSlot && .RelSlot == )if ! {continue }if .Max == nil || .Min == 0 || .MonoHop != nil {returnfalse } .MonoHop = &MonoHopSpec{RelKey: , Ascending: , NullsPass: }returntrue } }returnfalse}func isVar( ast.Expr, string) bool { , := .(*ast.Var)return && .Name == }func asVarName( ast.Expr) string {if , := .(*ast.Var); {return .Name }return""}// intLitVal returns an integer literal's value.func intLitVal( ast.Expr) (int64, bool) { , := .(*ast.Lit)if ! || .Value.Kind != ast.LitInt {return0, false }return .Value.I, true}func isIntLit( ast.Expr, int64) bool { , := intLitVal()return && == }func isZero( ast.Expr) bool { returnisIntLit(, 0) }
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.