// ORDER BY and pagination for materialized projection rows: a// decorate-sort-undecorate that evaluates each row's key vector once,// with a typed fast path (numeric/entity key columns pack into// order-preserving words so the comparator is pure word compares) and a// bounded top-k selection under LIMIT. Split from project.go, which holds// the streaming projection sink.
package execimport ()// sortRowsByOrder orders outs by proj.OrderBy, decorate-sort-undecorate: each// row's key vector is evaluated once up front, so the comparator does no// per-comparison evaluation or allocation (an ORDER BY key would otherwise be// re-evaluated O(rows log rows) times). matchedAt supplies each row's matched// prefix (nil when no key needs it) and base is that prefix's width, so a// non-projected key expression reads a reused combined-row buffer under an// invariant column scope.func sortRowsByOrder( *eval.Ctx, *plan.ProjPlan, map[string]int, func(int) []value.Value, int, [][]value.Value) [][]value.Value { := len(.OrderBy) := make([]int, )for := range .OrderBy { [] = plan.OrderColIndex(.OrderBy[].Expr, .Columns, .Returns) } := make(map[string]int, len()+len(.Columns))maps.Copy(, )for , := range .Columns { [] = + } := make([]value.Value, len()*)var []value.Valuefor := range { := falsefor := range .OrderBy {if [] >= 0 { [*+] = [][[]]continue }if ! { = append([:0], ()...) = append(, []...) = true } [*+] = eval.Eval(, .OrderBy[].Expr, , ) } } := make([]int, len())for := range { [] = }// Typed key columns: a key column homogeneous over OrderCmp's numeric // tier (Int/Float) or over one entity kind compares through its // primitive encoding -- the identical kernel (value.TotalOrderF64 / // id order), skipping the boxed rank dispatch per comparison. Any // other mix keeps value.OrderCmp.const ( = uint8(iota) ) := make([]uint8, )var []float64var []uint64iflen() > 0 {for := range { , , := true, true, truefor := range {switch [*+].Kind() {casevalue.KindInt, value.KindFloat: , = false, falsecasevalue.KindNode: , = false, falsecasevalue.KindRel: , = false, falsedefault: , , = false, false, false }if ! && ! && ! {break } }switch {case : [] = if == nil { = make([]float64, len()*) }for := range { [*+] = value.OrderNumF64([*+]) }case || : [] = if == nil { = make([]uint64, len()*) }for := range {varuint64if , := [*+].AsNode(); { = uint64(uint32()) } elseif , := [*+].AsRel(); { = uint64(uint32()) } [*+] = } } } }// The index tiebreak makes cmp a total order, so an unstable generic // sort (no reflection-based swapping) reproduces stable-sort output // exactly -- and a total order is what lets the bounded selection // below equal sort-then-truncate. := func(, int) int { , := *, *for := range .OrderBy {varintswitch [] {case : = value.TotalOrderF64([+], [+])case : , := [+], [+]switch {case < : = -1case > : = 1 }default: = value.OrderCmp([+], [+]) }if .OrderBy[].Desc { = - }if != 0 {return } }return - }// When every key column is typed, the key vector packs into // order-preserving uint64 words -- numeric through the IEEE-754 // totalOrder monotone encoding (the same relation TotalOrderF64 // computes per comparison, applied once per key instead), entity ids // directly, descending baked in by complement -- so the comparator is // pure word compares. Result order is identical to the typed // comparator above by monotonicity of the encodings. := len() > 0for := range {if [] == { = falsebreak } }if { := make([]uint64, len()*)for := range { := .OrderBy[].Descfor := range {varuint64if [] == { = packSortWordF64([*+]) } else { = [*+] }if { = ^ } [*+] = } } = func(, int) int { , := *, *for := 0; < ; ++ { , := [+], [+]if != {if < {return -1 }return1 } }return - } }// Under ORDER BY + LIMIT, pagination consumes only the leading // skip+limit rows: select those with a bounded heap (one comparison // per rejected row) instead of sorting everything.if := orderBound(); >= 0 && < len() { = topKIdx(, , ) }slices.SortFunc(, ) := make([][]value.Value, len())for , := range { [] = [] }return}// packSortWordF64 is the IEEE-754 totalOrder monotone uint64 encoding:// unsigned comparison of the encoded words equals value.TotalOrderF64 on// the raw floats (negatives complement fully; non-negatives set the sign// bit), so a packed key column sorts through plain word compares.func packSortWordF64( float64) uint64 { := math.Float64bits()if &(1<<63) != 0 {return ^ }return | 1<<63}// orderBound is the row count pagination can consume after an ordered// projection (skip+limit), or -1 when unbounded.func orderBound( *plan.ProjPlan) int {if .Limit == nil {return -1 } := *.Limitif .Skip != nil { += *.Skip }if > uint64(^uint(0)>>1) {return -1 }returnint()}// topKIdx selects the k smallest elements of idx under the total order// cmp, order among them unspecified (the caller sorts the survivors). A// size-k max-heap over the prefix; every further candidate is rejected// with a single comparison against the current k-th unless it improves// the set. Equal to sort-then-truncate because cmp is total.func topKIdx( []int, int, func(, int) int) []int {if == 0 {return [:0] } := [:]for := /2 - 1; >= 0; -- {siftDownIdx(, , ) }for , := range [:] {if (, [0]) < 0 { [0] = siftDownIdx(, 0, ) } }return}// siftDownIdx restores the max-heap property below i.func siftDownIdx( []int, int, func(, int) int) {for { := 2* + 1if >= len() {return } := if := + 1; < len() && ([], []) > 0 { = }if ([], []) <= 0 {return } [], [] = [], [] = }}// paginate applies OFFSET/SKIP then LIMIT.func paginate( [][]value.Value, , *uint64) [][]value.Value {if != nil { := int(*)if >= len() {returnnil } = [:] }if != nil && uint64(len()) > * { = [:*] }return}
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.