// Core lowering primitives (port of the Rust plan/lower.rs): slot// assignment, scan-source selection, hop building, var-length// hop-predicate extraction, projection binding, and the shared AND-chain// helpers. The anchor-seek recognizers live in lower_seeks.go.
package planimport ()// assignSlot resolves a node pattern's slot: an existing variable keeps its// slot (wasBound reports whether it is already bound); a new or anonymous// node takes the next slot.func assignSlot( *ast.NodePat, map[string]int, map[int]bool, *int) ( int, bool) {if .Var != "" {if , := [.Var]; {return , [] } := * *++ [.Var] = return , false } := * *++return , false}// scanSource picks a fresh node's default scan source: the most selective// indexed property seek over its inline props and WHERE equalities (via// bestPropSeek), else a label scan, else all nodes.func scanSource( *ast.NodePat, ast.Expr, graph.Graph) ScanSource {if , := bestPropSeek(, , ); {if .inVals != nil {returnScanSource{Kind: ScanPropertyIn, Label: .Labels[0], Key: .key, Values: .inVals} }returnScanSource{Kind: ScanProperty, Label: .Labels[0], Key: .key, Value: .val} }iflen(.Labels) > 0 {returnScanSource{Kind: ScanLabel, Label: .Labels[0]} }returnScanSource{Kind: ScanAll}}// buildHop lowers one relationship hop to an Expand or VarExpand op.func buildHop( *ast.RelPat, , , int, bool, *ast.NodePat) (BindOp, error) { := BindOp{From: ,To: ,Rebind: ,Dir: mapDir(.Dir),Types: .Types,RelSlot: ,Labels: .Labels,Props: .Props, }if .Length == nil { .Kind = OpExpandreturn , nil }// GQL quantifier defaulting: an absent lower bound is 0 ({,3}, *), // per GRAMMAR.md -- unlike Cypher's *..3, whose min defaults to 1. := .Lengthvaruint64if .Min != nil { = *.Min }if .Max != nil && > *.Max {returnBindOp{}, planErrf("quantifier bound {%d,%d} is empty", , *.Max) }// Zero-length or unbounded resolves the distinct reachable set (a // dedup'd BFS), which has no per-path rel list, so a named rel // variable is not supported there. := == 0 || .Max == nilif && .Var != "" {returnBindOp{}, planErrf("a relationship variable is not supported on a zero-length or unbounded quantified pattern -- it resolves a reachable set, not paths") } .Kind = OpVarExpand .Min = .Max = .Max .RelVar = .Varreturn , nil}// extractVarlenHopPreds lifts all(r IN rels(e) WHERE pred) conjuncts from// a stage WHERE onto the matching var-expand as a per-hop filter; consumed// conjuncts drop from the WHERE. (Monotonic-order conjuncts lift later, in// the segment-wide pushMonoPreds pass.)func extractVarlenHopPreds( *ast.Expr, []BindOp) error {if * == nil {returnnil }var []ast.ExprSplitAnd(*, &)var []ast.Exprfor , := range { , := tryPushHopPred(, )if != nil {return }if {continue } = append(, ) } * = rebuildAnd()returnnil}// tryPushHopPred lifts a single all(r IN rels(e) WHERE pred-over-r)// conjunct onto the var-expand binding e. Any other shape returns false// and is handled generally (never rejected here).func tryPushHopPred( ast.Expr, []BindOp) (bool, error) { , := .(*ast.ListPred)if ! {returnfalse, nil } := relsArg(.List)if == "" {returnfalse, nil }var *BindOpfor := range {if [].Kind == OpVarExpand && [].RelVar == { = &[]break } }if == nil {returnfalse, nil }if .Quant != ast.QuantAll || predRefsOnly(.Pred, .Var) != nil {returnfalse, nil }if .RelPred != nil {returnfalse, nil } .RelPred = &RelHopPred{Var: .Var, Pred: .Pred}returntrue, nil}// relsArg matches rels(<var>) returning the variable name; "" otherwise.func relsArg( ast.Expr) string { , := .(*ast.Func)if ! || .Star || len(.Args) != 1 || !eqFold(.Name, "rels") {return"" } , := .Args[0].(*ast.Var)if ! {return"" }return .Name}// predRefsOnly validates that a per-hop predicate's free variables are// only the iteration variable v (so it can be evaluated against one// relationship).func predRefsOnly( ast.Expr, string) error {if := freeVarsOutside(, []string{}); len() > 0 {returnplanErrf("a per-hop predicate may only reference the relationship variable `%s` (found `%s`)", , [0]) }returnnil}// SplitAnd walks a WHERE's top-level AND chain, appending each conjunct// (exported: the executor's conjunct bucketing splits the same chains).func ( ast.Expr, *[]ast.Expr) {if , := .(*ast.Binary); && .Op == ast.OpAnd { (.LHS, ) (.RHS, )return } * = append(*, )}// rebuildAnd re-joins conjuncts left-associatively; nil for an empty list.func rebuildAnd( []ast.Expr) ast.Expr {iflen() == 0 {returnnil } := [0]for , := range [1:] { = &ast.Binary{Op: ast.OpAnd, LHS: , RHS: } }return}// mapDir converts pattern syntax direction to a traversal direction.func mapDir( ast.Dir) graph.Direction {switch {caseast.DirOut:returngraph.Outgoingcaseast.DirIn:returngraph.Incoming }returngraph.Both}
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.