// 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 exec import ( ) // 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 { case plan.AggCount: if ! || !.IsNull() { .count++ } case plan.AggSum: if , := .AsInt(); { .sumI.add() .any = true } else if .Kind() == value.KindFloat { , := .AsFloat() .sumF += .isFloat = true .any = true } case plan.AggAvg: if , := .AsFloat(); { .avgSum += .avgN++ } case plan.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 { case plan.AggCount: return value.Int(.count) case plan.AggSum: switch { case !.any: return value.Int(0) case .isFloat: return value.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(); { return value.Int() } return value.Null() case plan.AggAvg: if .avgN == 0 { return value.Null() } return value.Float(.avgSum / float64(.avgN)) case plan.AggStddevSamp: if .avgN < 2 { return value.Float(0) // Neo4j's stdev: 0 on empty/single } return value.Float(math.Sqrt(.sdM2 / float64(.avgN-1))) case plan.AggStddevPop: if .avgN == 0 { return value.Float(0) } return value.Float(math.Sqrt(.sdM2 / float64(.avgN))) } // AggMin/AggMax/AggCollect finalize off the aggregator's overflow slabs. return value.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() { case value.KindNode: , := .AsNode() return .addEntity(uint32(), false, &.nodes) case value.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 == { return false } } if int(.nSmall) < len(.small) { .small[.nSmall] = .nSmall++ return true } } 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 = nil break } , := [.Name] if ! { .keySlots = nil break } .keySlots = append(.keySlots, ) } } for , := range .Aggs { := -1 if .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 { case plan.AggMin, plan.AggMax: .hasMinMax = true case plan.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) { return tierGroups[] } return chunkGroups } // 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]: return 0, case < tierBounds[1]: return 1, - tierBounds[0] case < tierBounds[2]: return 2, - tierBounds[1] } := - tierBounds[2] return 3 + /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) { switch len() { case 1: return packGroupKey1([0]) case 2: return packGroupKey2([0], [1]) } return 0, false } // packGroupKey1 is packGroupKey's single-key form. func packGroupKey1( value.Value) (uint64, bool) { switch .Kind() { case value.KindInt: , := .AsInt() if < -(1<<61) || >= 1<<61 { return 0, false } return 0<<62 | uint64()&(1<<62-1), true case value.KindNode: , := .AsNode() return 1<<62 | uint64(uint32()), true case value.KindRel: , := .AsRel() return 2<<62 | uint64(uint32()), true } return 0, false } // packGroupKey2 is packGroupKey's entity-pair form. func packGroupKey2(, value.Value) (uint64, bool) { , := packedEntity30() , := packedEntity30() if && { return 3<<62 | <<31 | , true } return 0, 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) { var uint64 var uint64 switch .Kind() { case value.KindNode: , := .AsNode() = uint64(uint32()) case value.KindRel: , := .AsRel() , = uint64(uint32()), 1 default: return 0, false } if >= 1<<30 { return 0, 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() }) }