// Correlated subquery matching: EXISTS { MATCH ... }, COUNT { MATCH ... },// and pattern comprehensions, all sharing one anchored DFS over the// pattern. The DFS's evaluation-invariant setup (anchor-reversal, level// slots, the extended scope, compiled node and rel matchers) is shaped// once per pattern and cached on the Ctx with its evaluation scratch, so// the per-row cost is a row refresh instead of map/slice/matcher rebuilds.
package evalimport ()// engineDir maps a pattern direction to the engine's traversal direction.func engineDir( ast.Dir) graph.Direction {switch {caseast.DirOut:returngraph.Outgoingcaseast.DirIn:returngraph.Incoming }returngraph.Both}// evalExists is EXISTS { MATCH pattern [WHERE ...] } -- true if the// correlated subquery matches at least once.func evalExists( *Ctx, *ast.Pattern, ast.Expr, []value.Value, map[string]int) bool {returnSubqueryCount(, , , , , true) > 0}// evalCountSub is COUNT { MATCH pattern [WHERE ...] } -- how many times// the correlated subquery matches the current outer row.func evalCountSub( *Ctx, *ast.Pattern, ast.Expr, []value.Value, map[string]int) int64 {returnint64(SubqueryCount(, , , , , false))}// subqueryShape is the shared setup of the subquery DFS -- the pattern// (reversed when only its far end is anchored to an outer variable), the// per-level node patterns, their row slots, and which are outer-anchored// -- plus the reusable evaluation scratch (row buffer, per-level// candidates, BFS sets). Shapes cache on the Ctx keyed by pattern; validFor// re-checks the outer scope on every hit, so a pattern evaluated under a// different scope rebuilds instead of misbinding.type subqueryShape struct { pattern *ast.Pattern nodes []*ast.NodePat nodeSlots []int anchored []bool outer map[string]int outerRowW int slots map[string]int row []value.Value cand [][]graph.NodeID// scan0 memoizes the unanchored level-0 scan (evaluation-invariant: // labels and literal props only); scan0Done marks it filled. scan0 []graph.NodeID scan0Done bool// whereFn is the compiled per-row WHERE evaluator (Ctx.CompileWhere), // cached per shape; whereFor guards against a shape being reused with // a different WHERE expression. whereFn func(*Ctx, []value.Value) value.Value whereFor ast.Expr// matchers lazily holds each hop's pre-resolved rel matcher, so the // per-candidate seam calls skip name resolution. matchers []*graph.RelMatcher// nodeMatchers lazily holds each level's compiled node matcher (labels // resolved to membership tests, inline-prop params resolved to values), // so the per-candidate accept is a bitmap probe plus a column read // instead of a string-keyed label lookup -- the same hoist the main // expand path gets from CompileNodeMatcher, mirrored for the subquery // walk. Params are fixed per Ctx, so the resolved values stay valid for // the shape's lifetime (the invariant scan0 already relies on). nodeMatchers []*graph.NodeMatcher// BFS scratch for quantified hops. expanded map[graph.NodeID]struct{} emitted map[graph.NodeID]struct{} frontier []graph.NodeID next []graph.NodeID reach []graph.NodeID// rev is the reversed-pattern shape, built when BOTH endpoints anchor // to outer variables: the probe then starts per row from whichever // bound endpoint has the smaller first-hop fan-out (a hot hub on one // side -- e.g. a popular tag -- otherwise costs its whole neighbor // list on every row). rev *subqueryShape}// subqueryShapeFor returns the pattern's cached shape with its row buffer// refreshed from the outer row, building (and caching) it on first use.// With both endpoints anchored, the forward or reversed shape is chosen// per row by the bound endpoints' actual first-hop degree (O(1) reads).func subqueryShapeFor( *Ctx, map[string]int, []value.Value, *ast.Pattern, bool) *subqueryShape {if .subqShapes == nil { .subqShapes = map[*ast.Pattern]*subqueryShape{} } := .subqShapes[]if == nil || !.validFor(, ) { = buildSubqueryShape(, , , ) .subqShapes[] = }if .rev != nil {if , := [.nodeSlots[0]].AsNode(); {if , := [.rev.nodeSlots[0]].AsNode(); { := .G.Degree(, engineDir(.pattern.Hops[0].Rel.Dir)) := .G.Degree(, engineDir(.rev.pattern.Hops[0].Rel.Dir))if < { = .rev } } } }copy(.row, )for := .outerRowW; < len(.row); ++ { .row[] = value.Null() }return}// validFor reports whether the shape was built against an identical outer// scope and row width.func ( *subqueryShape) ( map[string]int, []value.Value) bool {returnlen() == .outerRowW && maps.Equal(, .outer)}// buildSubqueryShape anchors the DFS on a bound endpoint: if the start// node isn't an outer variable but the end node is, the pattern reverses// so expansion starts from the (few) bound node's neighbors rather than a// label scan. When BOTH endpoints are anchored, a reversed twin shape is// attached and subqueryShapeFor picks a side per row by actual degree.// When NEITHER endpoint is anchored, the walk seeds from the statically// stronger endpoint (property literal beats bare label beats unlabeled)// when the caller allows reversal -- an unlabeled written start would// otherwise seed the level-0 scan with the whole id space to reach a// seek-sized end. Reversal enumerates the same match set, so counting// callers are safe; a COLLECTING caller (pattern comprehension) must pass// allowSeedReverse=false, because reversing its walk reverses the list's// element order -- a visible output change. Outer variables keep their// slots; inner-only pattern variables extend the row.func buildSubqueryShape( map[string]int, []value.Value, *ast.Pattern, bool) *subqueryShape { := func( *ast.NodePat) bool {if .Var == "" {returnfalse } , := [.Var]return } := (&.Start) := (.EndNode())switch {case ! && : := .Reversed() = &case && ! && ! && len(.Hops) > 0 &&seedStrength(.EndNode()) > seedStrength(&.Start): := .Reversed() = & } := buildOneSubqueryShape(, , )if && && len(.Hops) > 0 { := .Reversed() .rev = buildOneSubqueryShape(, , &) }return}// seedStrength ranks a node pattern's static selectivity as a level-0// seed: an inline property ({name: 'x'} or a param) beats a bare label// beats an unlabeled node.func seedStrength( *ast.NodePat) int {iflen(.Props) > 0 || len(.PropExprs) > 0 {return2 }iflen(.Labels) > 0 || .LabelExpr != nil {return1 }return0}// buildOneSubqueryShape builds a single direction's shape.func buildOneSubqueryShape( map[string]int, []value.Value, *ast.Pattern) *subqueryShape { := &subqueryShape{pattern: ,outer: ,outerRowW: len(),slots: make(map[string]int, len()+len(.Hops)+1), }maps.Copy(.slots, ) .nodes = append(.nodes, &.Start)for := range .Hops { .nodes = append(.nodes, &.Hops[].Node) } := len()for , := range .nodes {switch {case .Var != "" && hasKey(.slots, .Var): .nodeSlots = append(.nodeSlots, .slots[.Var]) .anchored = append(.anchored, true)case .Var != "": .slots[.Var] = .nodeSlots = append(.nodeSlots, ) .anchored = append(.anchored, false) ++default: .nodeSlots = append(.nodeSlots, ) .anchored = append(.anchored, false) ++ } } .row = make([]value.Value, ) .cand = make([][]graph.NodeID, len(.nodes))return}func hasKey( map[string]int, string) bool { , := []return}// SubqueryCount counts matches of a correlated subquery against the// current outer row, anchoring pattern nodes that name an outer variable// to their bound value. stopAtFirst short-circuits for EXISTS. A single// fixed-length pattern (quantifiers are treated as one hop).func ( *Ctx, *ast.Pattern, ast.Expr, []value.Value, map[string]int, bool) int { := subqueryShapeFor(, , , , true) := .whereEval(, ) := 0 .dfs(, 0, func() bool {if == nil || (, .row).IsTruthy() { ++ }return && > 0 })return}// whereEval resolves the walk's WHERE evaluator: compiled once per shape// through Ctx.CompileWhere when the executor installed it, else the// per-row interpreter. subqWhereCompiles is the engagement oracle.func ( *subqueryShape) ( *Ctx, ast.Expr) func(*Ctx, []value.Value) value.Value {if == nil {returnnil }if .CompileWhere == nil || .ForceInterp {returnfunc( *Ctx, []value.Value) value.Value { returnEval(, , , .slots) } }if .whereFn == nil || .whereFor != { .whereFn = .CompileWhere(, .slots) .whereFor = subqWhereCompiles++ }return .whereFn}// subqWhereCompiles counts compiled subquery WHERE builds -- the// engagement oracle: a walk over N candidates must compile its WHERE// exactly once per shape, not per row; 0 across a filtered subquery// means every candidate paid the interpreter.var subqWhereCompiles int// SubqueryGroupCount evaluates a correlated subquery ONCE with the group// variable left free, returning the match count bucketed by the node the// group variable binds to. It is the decorrelated form of SubqueryCount:// where SubqueryCount re-walks the pattern once per outer entity bound to// the group variable, a single SubqueryGroupCount call -- anchored on the// SHARED endpoint (anchorVar, still bound from the outer row) -- yields every// entity's count in one pass over the anchored set, so the per-entity cost// collapses from a walk to an O(1) map read.//// anchorVar must name an outer variable bound in outerRow; groupVar names the// pattern endpoint to bucket by, left UNBOUND here even when it is itself an// outer variable (its outer binding is ignored -- enumeration rebinds it per// match). The returned map is keyed only by node id, so it is independent of// what the outer query calls the group variable: two subqueries with the same// pattern, WHERE, and anchor share one table (see task 084 / rustychickpeas// 091). The result for a given entity equals SubqueryCount with that entity// bound -- the invariant the decor parity test asserts.func ( *Ctx, *ast.Pattern, ast.Expr, []value.Value, map[string]int, , string) map[graph.NodeID]int {// Anchor only anchorVar: a reduced outer scope that drops groupVar so the // group endpoint enumerates through the DFS instead of binding. := make(map[string]int, len())for , := range {if != { [] = } } := buildSubqueryShape(, , , true)copy(.row, )for := len(); < len(.row); ++ { .row[] = value.Null() } := map[graph.NodeID]int{} , := .slots[]if ! {return } := .whereEval(, ) .dfs(, 0, func() bool {if == nil || (, .row).IsTruthy() {if , := .row[].AsNode(); { []++ } }returnfalse })return}// evalPatternComp collects a projection over each correlated match of a// pattern: [ (pattern) [WHERE filter] | proj ].func evalPatternComp( *Ctx, *ast.PatternComp, []value.Value, map[string]int) value.Value {// Collecting walk: list element order is enumeration order, so the // unanchored seed reversal must not fire here. := subqueryShapeFor(, , , .Pattern, false) := .whereEval(, .Where) := []value.Value{} .dfs(, 0, func() bool {if == nil || (, .row).IsTruthy() { = append(, Eval(, .Proj, .row, .slots)) }returnfalse })returnvalue.List()}
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.