// Non-aggregated projection: the terminal sink evaluates the output// columns per pushed row, applies DISTINCT on arrival (before ORDER// BY/LIMIT, as the standard requires -- first occurrence kept), then// finalize sorts and paginates. Projected rows live in a chunked arena;// the matched row is retained (arena-copied) only when an ORDER BY key is// not a projected column. The Rust bounded top-k heap remains a possible// follow-up with byte-identical results.
package execimport ()// projSink is the non-aggregated terminal sink.type projSink struct { ctx *eval.Ctx proj *plan.ProjPlan slots map[string]int returns []RowEval outs [][]value.Value oArena rowArena// needM: an ORDER BY key evaluates over the matched row, so matched // rows must be retained alongside their projections. needM bool ms [][]value.Value mArena rowArena// DISTINCT state: a single output column dedups through distinctSet // (the u32 entity-id fast path for node/rel values, AppendKey bytes // otherwise); a multi-column row keys on the concatenated AppendKey // encoding -- both dedups thus share one canonical value encoding. seenOne *distinctSet seen *flatset.ByteSet key []byte// topk is the streaming bounded accumulator under ORDER BY + LIMIT: // at most skip+limit rows are retained, ordered by the sort's exact // total order (keys, then arrival), so a rejected row costs one // comparison and leaves nothing in the arenas. limitCap is the // LIMIT-without-ORDER-BY retention cap (arrival order IS the output // order, so rows past skip+limit can never surface). topk *topKRows limitCap int// topk key evaluation state (mirrors sortRowsByOrder's scope). kColIdx []int kScope map[string]int kBase int kRowbuf []value.Value kBuf []value.Value// kGate marks the payload-gated top-k path: every ORDER BY key either // is an output column or evaluates against the matched row alone // (kRowEval, the RETURN m ... ORDER BY m.x identity family), and no // DISTINCT dedups on the full row -- so the keys can evaluate first // and the heap can refuse a candidate BEFORE its remaining columns are // built (most candidates die on one comparison once the heap fills). kGate bool kRowEval []RowEval}// topkPayloadBuilds counts full projected payload constructions on the// gated top-k path -- the load-independent oracle: ORDER BY v ASC LIMIT k// over ascending input builds exactly k payloads, not one per row.// disableTopkGate pins differential tests to the unguarded// build-then-offer flow (the reference the gate must match exactly).var ( topkPayloadBuilds int disableTopkGate bool)func newProjSink( *eval.Ctx, *plan.ProjPlan, map[string]int, int) *projSink { := &projSink{ctx: , proj: , slots: ,returns: make([]RowEval, len(.Returns)),oArena: rowArena{width: len(.Returns)},limitCap: -1, }for , := range .Returns { .returns[] = compileEval(, .Expr, ) }for := range .OrderBy {ifplan.OrderColIndex(.OrderBy[].Expr, .Columns, .Returns) < 0 { .needM = true .mArena = rowArena{width: }break } }if .Distinct {iflen(.Returns) == 1 { .seenOne = &distinctSet{} } else { .seen = &flatset.ByteSet{} } }if := orderBound(); >= 0 {// Retention is bounded, so size arena chunks to it (plus eviction // turnover) instead of the default sweep-sized chunk -- a // LIMIT-bounded sink otherwise pays a near-empty 16K-value chunk // per arena (the aggregate finalize precedent). .oArena.chunkValues = min(arenaChunkValues, max(64, 2*)*len(.Returns))if .needM { .mArena.chunkValues = min(arenaChunkValues, max(64, 2*)*) }if := len(.OrderBy); > 0 { .topk = newTopKRows(, , .OrderBy) .kColIdx = make([]int, )for := range .OrderBy { .kColIdx[] = plan.OrderColIndex(.OrderBy[].Expr, .Columns, .Returns) } .kScope = make(map[string]int, len()+len(.Columns))maps.Copy(.kScope, ) .kBase = 0if .needM { .kBase = }for , := range .Columns { .kScope[] = .kBase + } .kBuf = make([]value.Value, ) .kGate = !.Distinct && !disableTopkGatefor := range .OrderBy {if .kColIdx[] < 0 {// Not an output column, but still gateable when the // key's references all resolve in the matched row's // scope (RETURN m ... ORDER BY m.x and friends): it // then evaluates pre-build, exactly like a column key.ifsemantics.CheckRefs(.OrderBy[].Expr, ) == nil {if .kRowEval == nil { .kRowEval = make([]RowEval, ) } .kRowEval[] = compileEval(, .OrderBy[].Expr, )continue } .kGate = false// key needs the built rowbreak } } } else {// No ORDER BY: arrival order is output order, so retention // past skip+limit can never surface. .limitCap = } }return}// pushKeys evaluates the ORDER BY key vector for a just-projected row// into p.kBuf (out is the projected row, row the matched row).func ( *projSink) (, []value.Value) []value.Value { := falsefor := range .proj.OrderBy {if := .kColIdx[]; >= 0 { .kBuf[] = []continue }if ! { .kRowbuf = append(.kRowbuf[:0], ...) .kRowbuf = append(.kRowbuf, ...) = true } .kBuf[] = eval.Eval(.ctx, .proj.OrderBy[].Expr, .kRowbuf, .kScope) }return .kBuf}func ( *projSink) ( []value.Value) bool {if .limitCap >= 0 && len(.outs) >= .limitCap {// Arrival order is output order (no ORDER BY), so nothing a // producer could still emit can surface: stop the walk.returnfalse }// Gated top-k: evaluate only the key columns and ask the heap first; // a refused candidate never builds its remaining columns (nor touches // the arena). Skipping is byte-identical to offer-then-pop (see // wouldAccept), so the surviving rows match the unguarded path // exactly.if .kGate {for := range .proj.OrderBy {if := .kColIdx[]; >= 0 { .kBuf[] = .returns[].Eval(.ctx, , .slots) } else { .kBuf[] = .kRowEval[].Eval(.ctx, , .slots) } }if !.topk.wouldAccept(.kBuf) { .topk.seq++ // rejected offers still order future arrivalsreturntrue }topkPayloadBuilds++ := .oArena.alloc()for := range .proj.OrderBy {if := .kColIdx[]; >= 0 { [] = .kBuf[] } }for , := range .returns {if !.kGateCol() { [] = .Eval(.ctx, , .slots) } }if !.topk.offer(.pushKeys(, ), ) { .oArena.rollback() }returntrue } := .oArena.alloc()for , := range .returns { [] = .Eval(.ctx, , .slots) }if .seenOne != nil {if !.seenOne.add([0], &.key) { .oArena.rollback()returntrue } } elseif .seen != nil { .key = .key[:0]for , := range { .key = value.AppendKey(.key, ) }if !.seen.Add(.key) { .oArena.rollback()returntrue } }if .topk != nil {// Keys evaluate here, against the live matched row -- so the // matched row never needs retaining at all on this path.if !.topk.offer(.pushKeys(, ), ) { .oArena.rollback() }returntrue } .outs = append(.outs, )if .needM { .ms = append(.ms, .mArena.copyRow()) }// Reaching the unordered cap exactly now also stops the walk (a // deferred false would cost one more full candidate).return .limitCap < 0 || len(.outs) < .limitCap}// kGateCol reports whether output column i is one of the gated path's// already-evaluated key columns.func ( *projSink) ( int) bool {for := range .kColIdx {if .kColIdx[] == {returntrue } }returnfalse}func ( *projSink) () {}// topKRows is the sink's bounded ORDER BY + LIMIT accumulator: at most// bound rows survive, under the same total order finalize's sort applies// (ORDER BY keys, then arrival sequence), so streaming selection equals// materialize-sort-truncate exactly. A max-heap over parallel arrays;// the worst survivor sits at the root and each rejected offer costs one// comparison.type topKRows struct { bound int nk int desc []bool keys []value.Value// n*nk, admitted key vectors outs [][]value.Value seqs []int n int seq int}func newTopKRows(, int, []ast.SortItem) *topKRows { := &topKRows{bound: , nk: , desc: make([]bool, )}for := range { .desc[] = [].Desc }// The heap fills to bound and stays there; presizing (capped, so a // huge LIMIT over few offers does not overcommit) removes the append // regrowth of the three parallel arrays.if := min(, 1024); > 0 { .keys = make([]value.Value, 0, *) .outs = make([][]value.Value, 0, ) .seqs = make([]int, 0, ) }return}// cmpTo compares candidate (keys, seq) against admitted entry i.func ( *topKRows) ( []value.Value, , int) int {for := 0; < .nk; ++ { := value.OrderCmp([], .keys[*.nk+])if .desc[] { = - }if != 0 {return } }return - .seqs[]}// cmpEntries compares admitted entries a and b.func ( *topKRows) (, int) int {return .cmpTo(.keys[*.nk:(+1)*.nk], .seqs[], )}// offer admits the row when it belongs in the current top bound, copying// its keys (the caller's buffer is reused). Reports whether the row was// retained.// wouldAccept reports whether an offer carrying keys could be admitted,// consulted BEFORE the payload is built: true under capacity; once full,// only when keys sort strictly before the worst survivor. A key TIE// loses -- a new candidate's arrival sequence is larger than every// admitted row's, so a tied offer would be popped immediately -- which// makes skipping a rejected candidate byte-identical to offering it, and// preserves stability across a tie straddling the LIMIT boundary (the// kept rows' relative order never changes).func ( *topKRows) ( []value.Value) bool {if .bound == 0 {returnfalse }if .n < .bound {returntrue }for := 0; < .nk; ++ { := value.OrderCmp([], .keys[])if .desc[] { = - }if != 0 {return < 0 } }returnfalse}func ( *topKRows) ( []value.Value, []value.Value) bool { := .seq .seq++if .bound == 0 {returnfalse }if .n == .bound && .cmpTo(, , 0) >= 0 {returnfalse }if .n < .bound { .keys = append(.keys, ...) .outs = append(.outs, ) .seqs = append(.seqs, ) .n++ .siftUp(.n - 1)returntrue }copy(.keys[:.nk], ) .outs[0], .seqs[0] = , .siftDown(0)returntrue}func ( *topKRows) (, int) {for := 0; < .nk; ++ { .keys[*.nk+], .keys[*.nk+] = .keys[*.nk+], .keys[*.nk+] } .outs[], .outs[] = .outs[], .outs[] .seqs[], .seqs[] = .seqs[], .seqs[]}func ( *topKRows) ( int) {for > 0 { := ( - 1) / 2if .cmpEntries(, ) <= 0 {return } .swap(, ) = }}func ( *topKRows) ( int) {for { := 2* + 1if >= .n {return } := if := + 1; < .n && .cmpEntries(, ) > 0 { = }if .cmpEntries(, ) <= 0 {return } .swap(, ) = }}// sorted returns the survivors in final order (ascending under the total// order).func ( *topKRows) () [][]value.Value { := make([]int, .n)for := range { [] = }slices.SortFunc(, .cmpEntries) := make([][]value.Value, .n)for , := range { [] = .outs[] }return}func ( *projSink) () [][]value.Value {if .topk != nil {returnpaginate(.topk.sorted(), .proj.Skip, .proj.Limit) } := .outsiflen(.proj.OrderBy) > 0 { := func(int) []value.Value { returnnil } := 0if .needM { = func( int) []value.Value { return .ms[] }iflen(.ms) > 0 { = len(.ms[0]) } } = sortRowsByOrder(.ctx, .proj, .slots, , , ) }returnpaginate(, .proj.Skip, .proj.Limit)}
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.