// Anchor-seek recognizers (split from lower.go): the WHERE/inline-prop// conjunct forms that let a fresh node anchor on an index seek instead// of a label scan -- id equality (literal, param, and bound-variable),// substring text match, property equality, and property IN lists.
package planimport ()// idSeekLiteral recognizes a WHERE id(var) = <int|param> conjunct anchoring// var to a single node id; nil when absent. Either argument order; only// top-level AND conjuncts are inspected.func idSeekLiteral( ast.Expr, string) *ast.Literal {if == nil {returnnil }var []ast.ExprSplitAnd(, &)for , := range {if := idEqConjunct(, ); != nil {return } }returnnil}// textSeek is a recognized substring-index anchor predicate.type textSeek struct { field string mode ast.BinOp needle ast.Literal}// textMatchSeek recognizes a WHERE var.field {STARTS WITH|ENDS WITH|// CONTAINS} <string|param> conjunct so the anchor scan can be served from a// substring index. The conjunct is kept and re-checked.func textMatchSeek( ast.Expr, string) *textSeek {if == nil {returnnil }var []ast.ExprSplitAnd(, &)for , := range { , := .(*ast.Binary)if ! || (.Op != ast.OpStartsWith && .Op != ast.OpEndsWith && .Op != ast.OpContains) {continue } , := .LHS.(*ast.Prop)if ! || .Var != {continue } , := .RHS.(*ast.Lit)if ! {continue }switch .Value.Kind {caseast.LitStr, ast.LitParam, ast.LitNamedParam:return &textSeek{field: .Key, mode: .Op, needle: .Value} } }returnnil}// idEqConjunct matches one conjunct as id(var) = lit / lit = id(var) with// lit an Int / lifted Param / explicit NamedParam.func idEqConjunct( ast.Expr, string) *ast.Literal { , := .(*ast.Binary)if ! || .Op != ast.OpEq {returnnil } := func( ast.Expr) *ast.Literal { , := .(*ast.Lit)if ! {returnnil }switch .Value.Kind {caseast.LitInt, ast.LitParam, ast.LitNamedParam: := .Valuereturn & }returnnil }ifisIDOfVar(.LHS, ) {return (.RHS) }ifisIDOfVar(.RHS, ) {return (.LHS) }returnnil}// idSeekVar recognizes WHERE id(var) = <bound-var> (either order),// returning the bound variable's slot; NoSlot when absent.func idSeekVar( ast.Expr, string, map[string]int, map[int]bool) int {if == nil {returnNoSlot }var []ast.ExprSplitAnd(, &)for , := range {if := idEqVarConjunct(, , , ); != NoSlot {return } }returnNoSlot}// idEqVarConjunct matches id(var) = boundVar / boundVar = id(var),// returning the bound variable's slot (NoSlot otherwise).func idEqVarConjunct( ast.Expr, string, map[string]int, map[int]bool) int { , := .(*ast.Binary)if ! || .Op != ast.OpEq {returnNoSlot } := func( ast.Expr) int { , := .(*ast.Var)if ! || .Name == {returnNoSlot }if , := [.Name]; && [] {return }returnNoSlot }ifisIDOfVar(.LHS, ) {return (.RHS) }ifisIDOfVar(.RHS, ) {return (.LHS) }returnNoSlot}// isIDOfVar matches the single-argument id() function applied to exactly// varName.func isIDOfVar( ast.Expr, string) bool { , := .(*ast.Func)if ! || .Distinct || .Star || len(.Args) != 1 || !eqFold(.Name, "id") {returnfalse } , := .Args[0].(*ast.Var)return && .Name == }// propEq is a var-property equality recognized as an indexed-seek anchor:// key = val, val a seekable literal (Null excluded).type propEq struct { key string val ast.Literal}// propEqConjuncts collects the top-level WHERE conjuncts of the form// `varName.key = <literal|param>` (either operand order) -- the seekable// equalities the inline `{key: val}` spelling would have produced, so the two// spellings of one query plan alike. Only top-level AND conjuncts qualify: an// equality nested under an OR is not a guaranteed filter and must not lift.func propEqConjuncts( ast.Expr, string) []propEq {if == nil || == "" {returnnil }var []ast.ExprSplitAnd(, &)var []propEqfor , := range { , := .(*ast.Binary)if ! || .Op != ast.OpEq {continue }if , , := propEqSide(.LHS, .RHS, ); { = append(, propEq{key: , val: }) } elseif , , := propEqSide(.RHS, .LHS, ); { = append(, propEq{key: , val: }) } }return}// propEqSide matches propExpr as varName.key and litExpr as a seekable literal,// returning (key, value). A Null literal is rejected (= null is never true, so// it is not a seek). Params are accepted: they seek but abstain from costing.func propEqSide(, ast.Expr, string) (string, ast.Literal, bool) { , := .(*ast.Prop)if ! || .Var != {return"", ast.Literal{}, false } , := .(*ast.Lit)if ! {return"", ast.Literal{}, false }switch .Value.Kind {caseast.LitInt, ast.LitFloat, ast.LitStr, ast.LitBool, ast.LitParam, ast.LitNamedParam:return .Key, .Value, true }return"", ast.Literal{}, false}// propSeekPick is the property a labelled node anchors on via the value index,// chosen over both inline `{key: val}` props and top-level WHERE equalities on// the node. A concrete value carries its exact posting length; a param abstains// (no plan-time value) and is used only when nothing concrete seeks -- so a// param never bakes a value into a shared cached plan. inVals non-nil marks a// multi-value IN seek (card = summed posting lengths).type propSeekPick struct { key string val ast.Literal inVals []ast.Literal card uint64// exact posting length; meaningful only when !abstain abstain bool// param value: seekable but uncosted}// bestPropSeek is the single source of truth for which property a fresh// labelled node seeks on: the most selective one -- smallest exact posting// length -- across inline props and WHERE-form equalities. rank, anchorCard,// resolveAnchorNodes and scanSource all consult it, so the plan that is COSTED// is always the plan that is BUILT (the two drifting apart is the bug 107// names). A concrete prop always beats a param; among concretes, min posting// wins; ok=false for an unlabelled node or one with no seekable prop.func bestPropSeek( *ast.NodePat, ast.Expr, graph.Graph) (propSeekPick, bool) {iflen(.Labels) == 0 {returnpropSeekPick{}, false } := .Labels[0]varpropSeekPick := false := func( string, ast.Literal) {switch .Kind {caseast.LitNull:returncaseast.LitParam, ast.LitNamedParam:if ! { // a param seeks, but any concrete prop is preferred = propSeekPick{key: , val: , abstain: true} = true }return } := seekCard(, , , semantics.LitValue())if ! || .abstain || < .card { = propSeekPick{key: , val: , card: } = true } }for := range .Props { (.Props[].Key, .Props[].Val) }for , := rangepropEqConjuncts(, .Var) { (.key, .val) }for , := rangepropInConjuncts(, .Var) {varuint64for , := range .vals { += seekCard(, , .key, semantics.LitValue()) }if ! || .abstain || < .card { = propSeekPick{key: .key, inVals: .vals, card: } = true } }return , }// DisablePropInSeek pins result identity in tests: the label-scan +// filter evaluation must produce exactly the rows the IN seek does, in// the same order.varDisablePropInSeekbool// propIn is one `<var>.<key> IN [literals]` conjunct usable as a seek.type propIn struct { key string vals []ast.Literal}// propInConjuncts collects WHERE conjuncts of the form// `<var>.<key> IN [<literal>, ...]` whose every element is a string,// boolean, or numeric literal. IN compares int against float numerically// (30 IN [30.0] is true) while a property seek matches the stored value// exactly, so the seek probes each numeric element's twin alongside it// (seekNodes) -- without that a candidate the seek never yields is a row// silently lost, the one failure keep-and-re-check cannot catch.// (The sibling engine shipped the numeric form without twin probes and a// mixed int/float test caught it; rustychickpeas c07ce7f.)func propInConjuncts( ast.Expr, string) []propIn {ifDisablePropInSeek || == nil || == "" {returnnil }var []ast.ExprSplitAnd(, &)var []propInfor , := range { , := .(*ast.In)if ! {continue } , := .Expr.(*ast.Prop)if ! || .Var != {continue } , := .List.(*ast.ListExpr)if ! || len(.Elems) == 0 {continue } := make([]ast.Literal, 0, len(.Elems)) := truefor , := range .Elems { , := .(*ast.Lit)if ! { = falsebreak }switch .Value.Kind {caseast.LitStr, ast.LitBool, ast.LitInt, ast.LitFloat:default: = false }if ! {break } = append(, .Value) }if { = append(, propIn{key: .Key, vals: }) } }return}
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.