// Row-independent scan candidate sources: label/property/text-match/// id-seek/all-nodes (port of the Rust fresh_scan family).
package execimport ()// scanMatcherRedundant reports whether a fresh scan's per-candidate// matcher accept is provably redundant: a single-label scan whose op adds// no extra label or inline property already yields exactly the accepted// set.func scanMatcherRedundant( *plan.BindOp) bool {return .Source.Kind == plan.ScanLabel &&len(.Props) == 0 &&len(.Labels) == 1 && .Labels[0] == .Source.Label}// seekProbes is the set of index keys a single-value property seek must// probe for v: the value itself plus its numeric twin when one exists.func seekProbes( value.Value) []value.Value {if , := value.NumericTwin(); {return []value.Value{, } }return []value.Value{}}// freshScan appends a row-independent source's candidates, filtered// through the op's pre-resolved matcher unless provably redundant.func freshScan( *eval.Ctx, *plan.ScanSource, *graph.NodeMatcher, bool, *[]graph.NodeID) { := func( graph.NodeID) {if || .G.NodeMatcherAccepts(, ) { * = append(*, ) } }switch .Kind {caseplan.ScanProperty:// Resolve first (a param literal reads the context), then serve // the anchor from the property index. The index matches stored // values exactly while equality coerces int against float, so the // numeric twin of the resolved value is probed too -- without it a // float literal over an int-stored property (or the reverse) // silently loses every row. The two postings are disjoint (one // stored value per node/key); sorting restores ascending id order. := eval.LitValue(, .Value)var []graph.NodeIDfor , := rangeseekProbes() {if := .G.NodesWithProperty(.Label, .Key, ); != nil {for := range .Iter() { = append(, ) } } }slices.Sort()for , := range { () }caseplan.ScanPropertyIn:// One property seek per listed value plus its numeric twin, // unioned (IN compares int against float numerically, the index // matches exactly). Sorting restores the ascending order a label // scan yields (unordered results must not reshuffle) and dedup // collapses ids that satisfy more than one arm (duplicate or // twin-equal literals in the list); the kept IN conjunct re-checks // every candidate.var []graph.NodeIDfor , := range .Values {for , := rangeseekProbes(eval.LitValue(, )) {if := .G.NodesWithProperty(.Label, .Key, ); != nil {for := range .Iter() { = append(, ) } } } }slices.Sort()for , := rangeslices.Compact() { () }caseplan.ScanLabel:if := .G.NodesWithLabel(.Label); != nil {for := range .Iter() { () } }caseplan.ScanNodeID:// WHERE id(n) = <int|param>: the kept WHERE conjunct re-verifies, // so a non-integer or out-of-id-space value yields no candidate.if , := nodeIDSeekValue(, eval.LitValue(, .Value)); { () }caseplan.ScanTextMatch:// A substring-index candidate superset when the backend can prune, // else the whole label; the kept STARTS WITH/ENDS WITH/CONTAINS // conjunct verifies each candidate either way.if , := eval.LitValue(, .Value).AsStr(); {if , := .G.SubstringCandidates(.Label, .Field, ); {if != nil {for := range .Iter() { () } }return } }if := .G.NodesWithLabel(.Label); != nil {for := range .Iter() { () } }caseplan.ScanAll:// The id space pads to max-id+1, so a sparse space contains gap // ids that are not nodes; the existence oracle keeps them out // (dense graphs answer true for the whole space).for := graph.NodeID(0); < .G.IDSpace(); ++ {if .G.NodeExists() { () } } }}// seedFanoutCap bounds one EXISTS seed walk's frontier: past it the walk// is abandoned and the scan falls back to its base source for the row --// candidate-superset semantics make the bail free.const seedFanoutCap = 1 << 17// existsSeedCandidates enumerates the retained-EXISTS conjuncts'// candidate superset: each chain walks from its anchor's current row// value hop by hop over deduplicated ascending neighbor sets,// label-filtered per level, then the chains union (sort + compact) and// the op's own matcher applies. The kept WHERE finalizes, so like// ScanTextMatch this only narrows the scan. Reports false when a walk// exceeds the fan-out cap (caller falls back to the base source).func existsSeedCandidates( *eval.Ctx, *plan.BindOp, *graph.NodeMatcher, [][]*graph.RelMatcher, [][]*graph.NodeMatcher, []value.Value, *[]graph.NodeID, *genScratch) bool { := len(*)for := range .Source.Seeds { := &.Source.Seeds[] , := [.AnchorSlot].AsNode()if ! {continue } := append(.seedFrontier[:0], ) := .seedNext[:0]for := range .Hops { = [:0]for , := range { = .G.AppendNeighborsMatched(, , .Hops[].Dir, [][])iflen() > seedFanoutCap { .seedFrontier, .seedNext = , * = (*)[:]returnfalse } }slices.Sort() = slices.Compact() := [:0]for , := range {if .G.NodeMatcherAccepts([][], ) { = append(, ) } } , = , } * = append(*, ...) .seedFrontier, .seedNext = , } := (*)[:]slices.Sort() = slices.Compact() := [:0]for , := range {if .G.NodeMatcherAccepts(, ) { = append(, ) } } * = (*)[:+len()]returntrue}// nodeIDSeekValue resolves an id-seek value: an in-id-space non-negative// integer, comma-ok. The id space (not the node count) bounds it so sparse// high-id seeds still resolve. An integral float resolves through its int// twin -- a parameter can spell an id as 3.0, and equality coerces, so a// seek that declined it would silently lose the row.func nodeIDSeekValue( *eval.Ctx, value.Value) (graph.NodeID, bool) {if .Kind() == value.KindFloat { , := value.NumericTwin()if ! {return0, false } = } , := .AsInt()if ! || < 0 || uint64() >= uint64(.G.IDSpace()) {return0, false }returngraph.NodeID(), true}
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.