// 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 plan import // 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 == "" { return false } return applyMonoTarget([]Stage{}, .ref.relVar, NoSlot, .ref.key, .ascending, .nullsPass) }) } if .PostWhere != nil { .PostWhere = consumeMonoConjuncts(.PostWhere, func( monoShape) bool { if .ref.alias == "" { return false } return pushMonoAlias(, , ) }) } } } // 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.Expr SplitAnd(, &) var []ast.Expr for , := range { if , := monoConjunctShape(); && () { continue } = append(, ) } return rebuildAnd() } // 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 ! { return false } if , , := derivedListSourceExpr(); { , , := resolveMonoTarget([].Stages, [].Slots, ) if ! { return false } return applyMonoTarget([].Stages, , , , .ascending, .nullsPass) } if !isVar(, .ref.alias) { return false } } return false } // 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 { return monoPairCore(.Var, .List, .Pred, false) } , := .(*ast.Binary) if ! || .Op != ast.OpEq { return monoShape{}, false } var ast.Expr switch { case isZero(.RHS) && !isZero(.LHS): = .LHS case isZero(.LHS) && !isZero(.RHS): = .RHS default: return monoShape{}, false } , := sizeArg().(*ast.ListComp) if ! || .Filter == nil || .Map != nil { return monoShape{}, false } return monoPairCore(.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 ! { return monoShape{}, false } , := .(*ast.Binary) if ! { return monoShape{}, false } , , := monoElem(.LHS, ) , , := monoElem(.RHS, ) if ! || ! || != || !sameMonoList(, ) { return monoShape{}, false } := - if != 1 && != -1 { return monoShape{}, false } := < var bool if { switch .Op { case ast.OpGte: = // violation: earlier >= later case ast.OpLte: = ! default: return monoShape{}, false } } else { switch .Op { case ast.OpLt: = // order: earlier < later case ast.OpGt: = ! default: return monoShape{}, false } } := min(, ) if + != 0 { return monoShape{}, false } if := + ; ( && < -2) || (! && != -2) { return monoShape{}, false } return monoShape{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 ! { return monoListRef{}, 0, false } , := affineOffset(.Idx, ) if ! { return monoListRef{}, 0, false } if != "" { if := relsArg(.Base); != "" { return monoListRef{relVar: , key: }, , true } return monoListRef{}, 0, false } if := asVarName(.Base); != "" { return monoListRef{alias: }, , true } return monoListRef{}, 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 != "" { return isVar(, .alias) } return relsArg() == .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") { return 0, nil, 0, false } , := intLitVal(.Args[0]) if ! { return 0, nil, 0, false } , , := sizePlus(.Args[1]) if ! { return 0, 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 ! { return nil, 0, false } switch .Op { case ast.OpAdd: if := sizeArg(.LHS); != nil { if , := intLitVal(.RHS); { return , , true } } if := sizeArg(.RHS); != nil { if , := intLitVal(.LHS); { return , , true } } case ast.OpSub: if := sizeArg(.LHS); != nil { if , := intLitVal(.RHS); { return , -, true } } } return nil, 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) { if isVar(, ) { return 0, true } , := .(*ast.Binary) if ! { return 0, false } switch .Op { case ast.OpAdd: if isVar(.LHS, ) { if , := intLitVal(.RHS); { return , true } } if isVar(.RHS, ) { if , := intLitVal(.LHS); { return , true } } case ast.OpSub: if isVar(.LHS, ) { if , := intLitVal(.RHS); { return -, true } } } return 0, false } // sizeArg matches size(<x>) returning <x>. func sizeArg( ast.Expr) ast.Expr { , := .(*ast.Func) if ! || .Star || len(.Args) != 1 || !eqFold(.Name, "size") { return nil } 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: if isVar(.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 } } return nil, 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 []*BindOp for := range .Ops { if .Ops[].Kind == OpExpand || .Ops[].Kind == OpVarExpand { = append(, &.Ops[]) } } if len() == 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 { return false } .MonoHop = &MonoHopSpec{RelKey: , Ascending: , NullsPass: } return true } } return false } 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 { return 0, false } return .Value.I, true } func isIntLit( ast.Expr, int64) bool { , := intLitVal() return && == } func isZero( ast.Expr) bool { return isIntLit(, 0) }