// The group-by aggregator: rows route to their group's accumulators// (implicit group-by-the-non-aggregate-keys), then one output row per// group finalizes with ordering/pagination. count/sum/avg/min/max/collect// with per-aggregate DISTINCT; nested-aggregate scalar wrappers read// hidden accumulator slots and truncate them after. Encounter order of// groups is preserved (observable in unordered results).
package execimport ()// aggState is one aggregate accumulator, allocated once per group per// aggregate. It carries only the count/sum/avg scalars -- the kind-specific// heavy state (a min/max extremum value.Value, a collect []value.Value) lives// in slabs allocated on the aggregator only when those kinds are present, so// the overwhelmingly common count/sum/avg grouping pays neither. Fields are// ordered wide-to-narrow so the booleans pack into one tail word.type aggState struct { sumI acc128 count int64 sumF float64 avgSum float64 avgN int64 sdMean float64 sdM2 float64 kind plan.AggKind isFloat bool any bool}// update folds one argument in (arg absent means count(*)).func ( *aggState) ( value.Value, bool) {switch .kind {caseplan.AggCount:if ! || !.IsNull() { .count++ }caseplan.AggSum:if , := .AsInt(); { .sumI.add() .any = true } elseif .Kind() == value.KindFloat { , := .AsFloat() .sumF += .isFloat = true .any = true }caseplan.AggAvg:if , := .AsFloat(); { .avgSum += .avgN++ }caseplan.AggStddevSamp, plan.AggStddevPop:// Welford: numerically stable single pass; non-numeric args skip, // like avg.if , := .AsFloat(); { .avgN++ := - .sdMean .sdMean += / float64(.avgN) .sdM2 += * ( - .sdMean) } }// AggMin/AggMax/AggCollect are folded on the aggregator's overflow slabs, // not here (their heavy state is off the per-group struct).}// finalize emits the accumulator's value.func ( *aggState) () value.Value {switch .kind {caseplan.AggCount:returnvalue.Int(.count)caseplan.AggSum:switch {case !.any:returnvalue.Int(0)case .isFloat:returnvalue.Float(.sumF + .sumI.float64()) }// A true total outside int64 range is Null, matching the engine's // overflow policy (no per-row error channel) and the core // aggregate's nil Sum.if , := .sumI.int64(); {returnvalue.Int() }returnvalue.Null()caseplan.AggAvg:if .avgN == 0 {returnvalue.Null() }returnvalue.Float(.avgSum / float64(.avgN))caseplan.AggStddevSamp:if .avgN < 2 {returnvalue.Float(0) // Neo4j's stdev: 0 on empty/single }returnvalue.Float(math.Sqrt(.sdM2 / float64(.avgN-1)))caseplan.AggStddevPop:if .avgN == 0 {returnvalue.Float(0) }returnvalue.Float(math.Sqrt(.sdM2 / float64(.avgN))) }// AggMin/AggMax/AggCollect finalize off the aggregator's overflow slabs.returnvalue.Null()}// Group state lives in flat stride-indexed slabs on the aggregator (keys,// accumulators, DISTINCT sets) rather than per-group heap objects -- the// grouping itself stays keyed on the canonical value encoding, identical// to the Rust GroupKey set; the packed-uint64 index below is a perf-only// representation, like the distinctSet entity-id fast path.// distinctSet is one aggregate's DISTINCT dedup set. Node and relationship// values -- the overwhelmingly common DISTINCT columns -- probe a compact// entity-id set keyed on the raw u32 (the Rust engine's entity-id fast// path), which allocates nothing per insert and stays small. Every other// kind falls back to the canonical AppendKey byte string, exactly the prior// behavior. The three maps are created lazily, so a uniform distinct column// (the common case) holds exactly one.type distinctSet struct {// small is the inline entity-id form: most DISTINCT groups hold a // handful of entities, and a map per group is the dominant // aggregation allocation on entity-heavy groupings. Linear membership // over the inline array is faster than a map at this size; the first // overflow spills into the map form with identical semantics. The // first-seen entity kind claims the array (smRel marks a rel claim) -- // a mixed node/rel column, which no plan produces, sends the other // kind straight to its map so the two id spaces never conflate. nSmall uint8 smRel bool small [8]uint32 nodes flatset.U32Set rels flatset.U32Set other flatset.ByteSet}// add reports whether v is newly seen (and records it), reusing scratch for// the byte-string fallback encoding. Node/rel identity is exact u32// equality, matching AppendKey's tagNode/tagRel + u32 encoding; the two// entity kinds key separate stores so a node and a relationship of equal// id never conflate.func ( *distinctSet) ( value.Value, *[]byte) bool {switch .Kind() {casevalue.KindNode: , := .AsNode()return .addEntity(uint32(), false, &.nodes)casevalue.KindRel: , := .AsRel()return .addEntity(uint32(), true, &.rels) } * = value.AppendKey((*)[:0], )return .other.Add(*)}// addEntity dedups one entity id through the inline array (when this kind// holds the claim) or the kind's probe set, spilling the inline ids into// the set on overflow.func ( *distinctSet) ( uint32, bool, *flatset.U32Set) bool {if !.Built() {if .nSmall == 0 || .smRel == { .smRel = for , := range .small[:.nSmall] {if == {returnfalse } }ifint(.nSmall) < len(.small) { .small[.nSmall] = .nSmall++returntrue } }if .smRel == {for , := range .small[:.nSmall] { .Add() } } }return .Add()}// aggregator is the single-pass group-by accumulator. Group state lives in// flat slabs indexed by group number at strides len(groupC)/len(aggC), so// a NEW group costs amortized slab growth instead of per-group heap// objects; group keys that pack into a uint64 (entity ids and 62-bit ints,// the common grouping columns) route through an integer-keyed index whose// inserts allocate nothing, and only unpackable keys pay the byte-string// map. A key tuple packs (or not) purely by its values, so the two maps// never split a logical group.type aggregator struct { groupC []RowEval aggC []RowEval// nil entry = count(*) index flatset.ByteMap indexI flatset.U64Map// keySlots holds each group key's row slot when EVERY key is a bare // variable reference (and the tuple is short enough to pack), else // nil. A bare variable's evaluation is exactly the row slot's value, // so the update hit path packs entity ids straight off the row and // probes -- skipping the per-row key evaluation and buffering that a // group HIT (the dominant case) would only throw away. Any row whose // slots don't pack, and every miss, falls through to the unchanged // generic path, so claim/seed logic exists once. keySlots []int// argSlots holds each aggregate argument's row slot when that argument // is a bare variable reference (-1 otherwise): the per-row argument // "evaluation" is then a direct slot read instead of a compiled-eval // dispatch. Same identity argument as keySlots. argSlots []int nGroups int keysChunks [][]value.Value stateChunks [][]aggState seenChunks [][]distinctSet// filled only when a DISTINCT aggregate exists mmChunks [][]value.Value// min/max extrema, filled only when a min/max aggregate exists itemsChunks [][][]value.Value// collect lists, filled only when a collect aggregate exists hasDistinct bool hasMinMax bool hasCollect bool kinds []plan.AggKind// pctC holds each percentile aggregate's compiled constant second // argument (nil for every other kind), evaluated once at finalize. pctC []RowEval// keyScratch/gkScratch/dkScratch are the per-update key buffers, reused // so routing a row to an existing group (or a seen DISTINCT value) // allocates nothing. keyScratch []value.Value gkScratch []byte dkScratch []byte// rec recycles DISTINCT-set slot arrays across this aggregation's // groups: thousands of per-group sets climb the same growth ladder, // and without pooling every doubling's outgrown array is garbage -- // the dominant allocation of entity-DISTINCT aggregations over large // groups. rec flatset.Recycle}func newAggregator( *eval.Ctx, *plan.ProjPlan, map[string]int) *aggregator { := &aggregator{}for , := range .GroupIdx { .groupC = append(.groupC, compileEval(, .Returns[].Expr, )) }if := len(.GroupIdx); == 1 || == 2 { .keySlots = make([]int, 0, )for , := range .GroupIdx { , := .Returns[].Expr.(*ast.Var)if ! { .keySlots = nilbreak } , := [.Name]if ! { .keySlots = nilbreak } .keySlots = append(.keySlots, ) } }for , := range .Aggs { := -1if .Arg != nil { .aggC = append(.aggC, compileEval(, .Arg, ))if , := .Arg.(*ast.Var); {if , := [.Name]; { = } } } else { .aggC = append(.aggC, nil) } .argSlots = append(.argSlots, ) .kinds = append(.kinds, .Kind)if .Distinct { .hasDistinct = true }switch .Kind {caseplan.AggMin, plan.AggMax: .hasMinMax = truecaseplan.AggCollect, plan.AggPercentileCont, plan.AggPercentileDisc: .hasCollect = true }if .Arg2 != nil { .pctC = append(.pctC, compileEval(, .Arg2, )) } else { .pctC = append(.pctC, nil) } }return}// chunkGroups is the slab chunk size in groups: chunks allocate once and// never move, so group state pays no growth-copy bytes and at most one// partial chunk of waste.const chunkGroups = 4096// tierGroups are the geometric sizes of the first slab chunks: most// aggregates group into tens-to-hundreds of rows, and a full-size first// slab dominated whole-query allocation on small aggregates (task 205// round 5: Q8's 218-group aggregate paid 0.63 MB of slab capacity per// run). A large aggregate pays at most these three extra chunk seams.var tierGroups = [3]int{128, 512, 2048}// tierBounds are tierGroups' cumulative ends: the group index where each// later chunk starts.var tierBounds = [3]int{128, 640, 2688}// chunkCap is chunk c's size in groups.func chunkCap( int) int {if < len(tierGroups) {returntierGroups[] }returnchunkGroups}// chunkWindow maps a group index to its slab chunk and in-chunk group// offset: the first chunks grow geometrically per tierGroups, every// later chunk holds chunkGroups.func chunkWindow( int) (, int) {switch {case < tierBounds[0]:return0, case < tierBounds[1]:return1, - tierBounds[0]case < tierBounds[2]:return2, - tierBounds[1] } := - tierBounds[2]return3 + /chunkGroups, % chunkGroups}// keysOf/statesOf/seenOf are a group's slab windows.func ( *aggregator) ( int) []value.Value { := len(.groupC) , := chunkWindow()return .keysChunks[][* : (+1)*]}func ( *aggregator) ( int) []aggState { := len(.aggC) , := chunkWindow()return .stateChunks[][* : (+1)*]}func ( *aggregator) ( int) []distinctSet { := len(.aggC) , := chunkWindow()return .seenChunks[][* : (+1)*]}func ( *aggregator) ( int) []value.Value { := len(.aggC) , := chunkWindow()return .mmChunks[][* : (+1)*]}func ( *aggregator) ( int) [][]value.Value { := len(.aggC) , := chunkWindow()return .itemsChunks[][* : (+1)*]}// appendGroup claims the next slab windows for a new group, copying its// key tuple in.func ( *aggregator) ( []value.Value) int { := .nGroups .nGroups++ , := chunkWindow()if == 0 { := chunkCap() .keysChunks = append(.keysChunks, make([]value.Value, 0, *len(.groupC))) .stateChunks = append(.stateChunks, make([]aggState, 0, *len(.aggC)))if .hasDistinct { := make([]distinctSet, *len(.aggC))for := range { [].nodes.Rec = &.rec [].rels.Rec = &.rec } .seenChunks = append(.seenChunks, ) }if .hasMinMax { .mmChunks = append(.mmChunks, make([]value.Value, *len(.aggC))) }if .hasCollect { .itemsChunks = append(.itemsChunks, make([][]value.Value, *len(.aggC))) } } .keysChunks[] = append(.keysChunks[], ...)for , := range .kinds { .stateChunks[] = append(.stateChunks[], aggState{kind: }) }return}// packGroupKey packs a group-key tuple into a uint64: a single entity id// or 62-bit int, or a pair of entity ids below 2^30. Packing is a pure// function of the values, and the 2-bit shape tag keeps the int, node,// rel, and pair key spaces disjoint (mirroring AppendKey's kind tags).func packGroupKey( []value.Value) (uint64, bool) {switchlen() {case1:returnpackGroupKey1([0])case2:returnpackGroupKey2([0], [1]) }return0, false}// packGroupKey1 is packGroupKey's single-key form.func packGroupKey1( value.Value) (uint64, bool) {switch .Kind() {casevalue.KindInt: , := .AsInt()if < -(1<<61) || >= 1<<61 {return0, false }return0<<62 | uint64()&(1<<62-1), truecasevalue.KindNode: , := .AsNode()return1<<62 | uint64(uint32()), truecasevalue.KindRel: , := .AsRel()return2<<62 | uint64(uint32()), true }return0, false}// packGroupKey2 is packGroupKey's entity-pair form.func packGroupKey2(, value.Value) (uint64, bool) { , := packedEntity30() , := packedEntity30()if && {return3<<62 | <<31 | , true }return0, false}// packedEntity30 packs a node/rel id below 2^30 with its kind bit into 31// bits, for the pair form of packGroupKey.func packedEntity30( value.Value) (uint64, bool) {varuint64varuint64switch .Kind() {casevalue.KindNode: , := .AsNode() = uint64(uint32())casevalue.KindRel: , := .AsRel() , = uint64(uint32()), 1default:return0, false }if >= 1<<30 {return0, false }return <<30 | , true}// groupIdx routes a key tuple to its group's slab index, creating the// group on first sight.func ( *aggregator) ( []value.Value) int {if , := packGroupKey(); {return .indexI.GetOrCreate(, func() int { return .appendGroup() }) } := .gkScratch[:0]for , := range { = value.AppendKey(, ) } .gkScratch = return .index.GetOrCreate(, func() int { return .appendGroup() })}
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.