// Quantified (variable-length) expansion: bounded enumeration walks// relationship-unique trails (one row per path, GQL TRAIL semantics);// zero-length or unbounded quantifiers resolve the distinct reachable set// via a dedup'd BFS so a cyclic walk terminates. Each walk mode threads// the same hopGate seam: a stateless per-hop predicate and a stateful// carry+accept constraint prune during the walk.
package execimport ()// hopGate bundles a var-expand op's compiled per-hop constraints: an// optional stateless relationship predicate and an optional stateful// carry. It is the one seam every walk mode shares, so a future// carried-state constraint (sum-bounded, non-decreasing weight) extends// hopCarry instead of threading another field and parameter set.type hopGate struct { pred *hopFilter carry *hopCarry}// hopFilter is a compiled per-hop relationship predicate lifted from// all(r IN rels(e) WHERE pred): the iteration variable reads from slot 0// of a one-element row per traversed relationship. The row is a reused// scratch on the filter -- a fresh slice per keep would allocate once per// traversed relationship, and the gate is compiled per execution so the// scratch is never shared.type hopFilter struct { eval RowEval scope map[string]int srow [1]value.Value}func ( *hopFilter) ( *eval.Ctx, uint32) bool { .srow[0] = value.Rel()return .eval.Eval(, .srow[:], .scope).IsTruthy()}// carryState is the opaque per-path state a walk threads hop to hop for// its gate's carry; the zero value means "no state yet" (a path's first// hop has nothing to compare against).type carryState struct { val value.Value have bool}// hopCarry is the stateful per-hop constraint: step reads the candidate// hop's carried value and either extends the path's state or rejects the// hop. The monotonic-key constraint is the only instance today -- state// is the previous hop's key, compared with the filter's own three-valued// value.Compare so the pruned set exactly matches the source filter: an// incomparable pair (missing key, NaN, mixed kinds) fails an all()-shape// comparison and prunes, but is not a violation in the violation-count// shape and passes (nullsPass). Carry is meaningless for the reachability// BFS (dedup collapses paths, so there is no per-path state); the planner// never attaches a spec to a min-0/unbounded op.type hopCarry struct { eval RowEval scope map[string]int ascending bool nullsPass bool// srow is the reused one-element eval row (see hopFilter.srow): step // runs once per candidate hop, so a per-call slice would allocate on // every traversed relationship of the walk. srow [1]value.Value}// step consumes one hop: the returned state carries the hop's value, and// ok reports whether the hop may follow the previous state.func ( *hopCarry) ( *eval.Ctx, uint32, carryState) (carryState, bool) { .srow[0] = value.Rel() := .eval.Eval(, .srow[:], .scope)if .have && !.allows(.val, ) {return , false }returncarryState{val: , have: true}, true}// allows reports whether a hop whose key is cur may follow a hop whose key// is prev under the spec's order and null semantics.func ( *hopCarry) (, value.Value) bool { , := value.Compare(, )if ! {return .nullsPass }if .ascending {return < 0 }return > 0}// buildHopGates compiles each var-length op's per-hop constraints once for// the stage, indexed by op (the per-row form allocated a scope map and a// compiled tree on every row entering the walk).func buildHopGates( *eval.Ctx, []plan.BindOp) []hopGate { := make([]hopGate, len())for := range {if [].Kind != plan.OpVarExpand {continue }if := [].RelPred; != nil { := map[string]int{.Var: 0} [].pred = &hopFilter{eval: compileEval(, .Pred, ), scope: } }if := [].MonoHop; != nil { := map[string]int{"__r": 0} [].carry = &hopCarry{eval: compileEval(, &ast.Prop{Var: "__r", Key: .RelKey}, ),scope: ,ascending: .Ascending,nullsPass: .NullsPass, } } }return}// varExpandCandidates fills the candidate buffers for a quantified hop:// endpoint nodes, and for a named rel variable the flat rel arena plus// per-candidate (start, len) ranges. The walk state and dedup set live on// the stage's genScratch, reused across the row loop (the walk runs to// completion per call and is never nested, same as reachScratch).func varExpandCandidates( *eval.Ctx, *plan.BindOp, *graph.NodeMatcher, *graph.RelMatcher, hopGate, []value.Value, *uniqEnv, *[]graph.NodeID, *[]uint32, *[][2]int, *[][2]graph.NodeID, *[][2]int, *genScratch) { , := [.From].AsNode()if ! {return }vargraph.NodeID := falseif .Rebind { , = [.To].AsNode()if ! {return } }// A tracked op with LATER intersecting ops must expose each // candidate's trail pairs so they can be pushed for those ops to check // -- which forces the reach-shaped walk below into per-trail // enumeration. := .Uniq != nil && .Uniq.Contribute// Zero-length / unbounded quantifiers resolve the distinct reachable // set via dedup'd BFS, so an unbounded or cyclic walk can't explode // into per-path enumeration. Excluding the scope's used pairs during // the BFS keeps the endpoint SET exact: any walk in the pair-filtered // graph reduces to a trail avoiding those rels and vice versa.if (.Min == 0 || .Max == nil) && ! {// Chain collapse: a zero-minimum unbounded reach over a // functional rel type whose target label only chain terminals // carry is ONE root-array lookup -- the structural facts are // verified against the data and cached by the engine // (ChainCollapseVia), and the per-op resolution is cached per // execution. Per-hop predicates and rel-uniqueness checks keep // the general walk.if .Min == 0 && .Max == nil && .pred == nil && (.Uniq == nil || !.Uniq.Check) && len(.Labels) > 0 {if , := .chainRootsFor(, ); { := ifint() < len() { = [] }if (! || == ) && .G.NodeMatcherAccepts(, ) { * = append(*, ) }return } }// Functional chain walk: a functional type's reachable set is its // ancestor chain, so the general BFS's frontier and dedup maps // reduce to following the single rel -- honoring the same per-hop // predicate and rel-uniqueness checks per step. Falls back to the // BFS past chainWalkMax steps (a cycle in a functional type, or a // pathological chain).if .chainFuncFor(, ) &&chainReach(, , , , , , , , , , &.reach) {return }varReach(, , , , , , , , , , &.reach)return }// Contributing reach-shaped walks enumerate trails instead (finite: // a trail never repeats an edge pair). Min 0 also emits the // zero-length walk -- the start node itself, using no relationships. := unboundedHopsif .Max != nil { = *.Max }// Reset the persistent walk in place: the buffer fields keep their // backing arrays across rows. := &.walk * = varWalk{ctx: , op: , m: , rm: , gate: ,bound: , haveBound: ,collectRels: .RelSlot != plan.NoSlot,collectPairs: ,uniq: ,max: ,cand: , relData: , relRanges: ,pairData: , pairRanges: ,pathRels: .pathRels[:0], pathPairs: .pathPairs[:0], used: .used[:0], visited: .visited[:0],scratch: .scratch, nbufN: .nbufN[:0], nbufP: .nbufP[:0], }if .Acyclic { .visited = append(.visited, ) }if .Min == 0 && (! || == ) && .G.NodeMatcherAccepts(, ) { * = append(*, )if .collectRels { * = append(*, [2]int{len(*), 0}) }if { * = append(*, [2]int{len(*), 0}) } } .dfs(, 0, carryState{})// Without a named rel variable the trail's positions are not bound; // only then does endpoint dedup apply (first-seen order preserved).if !.collectRels {if .DedupEndpoints {if .dedup == nil { .dedup = make(map[graph.NodeID]struct{}, len(*)) } else {clear(.dedup) } := (*)[:0]for , := range * {if , := .dedup[]; ! { .dedup[] = struct{}{} = append(, ) } } * = } * = (*)[:0] * = (*)[:0] }}// varReach is the distinct nodes reachable in min..=max hops (max nil =// unbounded): a dedup'd BFS binding each reachable endpoint once. min 0// includes the start itself. expanded bounds the walk (each node's// neighbors explored once, so cycles terminate); emitted dedups the result// separately so a node re-reached via a cycle at depth >= min -- e.g. the// start when min >= 1 -- still emits once. The gate's stateless predicate// prunes edges here too; its carry never applies (see hopCarry).// chainWalkMax bounds the functional chain walk; a longer chain (or a// cycle, which a functional type can still form) falls back to the// dedup'd BFS. Real forest chains -- reply threads, org hierarchies --// sit far below it.const chainWalkMax = 64// chainReach is varReach for a FUNCTIONAL rel type: the reachable set is// the ancestor chain, so it follows the single rel per node with no// frontier or dedup state, applying the same bound/matcher acceptance,// hop-gate predicate, and rel-uniqueness exclusion per step. Reports// false (leaving out untouched) when the chain exceeds chainWalkMax --// the caller then runs the general walk.func chainReach( *eval.Ctx, graph.NodeID, *plan.BindOp, *graph.NodeMatcher, *graph.RelMatcher, hopGate, graph.NodeID, bool, *uniqEnv, *[]graph.NodeID, *reachScratch) bool { := uint64(1<<63 - 1)if .Max != nil { = *.Max } := func( graph.NodeID) bool {return (! || == ) && .G.NodeMatcherAccepts(, ) } := func(, graph.NodeID) bool {if .Uniq == nil || !.Uniq.Check {returntrue } , := uniqPair(.Dir, , )return !.used(.Uniq.Scope, , ) } := len(*)if .Min == 0 && () { * = append(*, ) } := for := uint64(0); < ; {if >= chainWalkMax { * = (*)[:]returnfalse } .nbuf, .pbuf = .G.AppendRelationshipsMatched(.nbuf[:0], .pbuf[:0], , .Dir, )iflen(.nbuf) == 0 {returntrue } := .nbuf[0]if .pred != nil && !.pred.keep(, .pbuf[0]) {returntrue }if !(, ) {returntrue } ++// A functional chain revisiting the start has cycled; every // reachable node is already emitted.if == {returntrue }if >= .Min && () { * = append(*, ) } = }returntrue}func varReach( *eval.Ctx, graph.NodeID, *plan.BindOp, *graph.NodeMatcher, *graph.RelMatcher, hopGate, graph.NodeID, bool, *uniqEnv, *[]graph.NodeID, *reachScratch) { := uint64(1<<63 - 1)if .Max != nil { = *.Max } := func( graph.NodeID) bool {return (! || == ) && .G.NodeMatcherAccepts(, ) }// A checking op excludes edges whose canonical pair the scope already // used (see varExpandCandidates). := func(, graph.NodeID) bool {if .Uniq == nil || !.Uniq.Check {returntrue } , := uniqPair(.Dir, , )return !.used(.Uniq.Scope, , ) }// Reuse the stage's working sets: clear the dedup maps and reset the // frontier/neighbor buffers rather than allocating them per row.if .expanded == nil { .expanded = map[graph.NodeID]struct{}{} .emitted = map[graph.NodeID]struct{}{} } else {clear(.expanded)clear(.emitted) } .expanded[] = struct{}{}if .Min == 0 && () { .emitted[] = struct{}{} * = append(*, ) } := append(.frontier[:0], ) := .next[:0]for := uint64(0); len() > 0 && < ; ++ { := + 1 = [:0]for , := range {if .pred != nil {// The predicate needs rel positions: walk relationships and // keep the passing edges' endpoints. .nbuf, .pbuf = .G.AppendRelationshipsMatched(.nbuf[:0], .pbuf[:0], , .Dir, ) := .nbuf[:0]for , := range .nbuf {if .pred.keep(, .pbuf[]) && (, ) { = append(, ) } } .nbuf = } else { .nbuf = .G.AppendNeighborsMatched(.nbuf[:0], , .Dir, )if .Uniq != nil && .Uniq.Check { := .nbuf[:0]for , := range .nbuf {if (, ) { = append(, ) } } .nbuf = } }for , := range .nbuf {if >= .Min && () {if , := .emitted[]; ! { .emitted[] = struct{}{} * = append(*, ) } }if , := .expanded[]; ! { .expanded[] = struct{}{} = append(, ) } } }// Swap the two persistent buffers: next becomes the frontier, the // old frontier is reused as next's backing. , = , } .frontier, .next = , }// unboundedHops is the trail walk's cap for an unbounded quantifier under// pair collection (the walk stays finite: a trail never repeats a pair).const unboundedHops = ^uint64(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.