// Fused columnar aggregation: a chain marked by plan/colagg.go -- one// bare label-scan segment, a run of stage-less LET boundaries, and a// stage-less aggregated boundary -- runs as ONE typed pass over the// scanned label's ids. Filters run through the per-candidate predicate// machinery, group keys and aggregate arguments through typed column// reads resolved across the boundary chain by name; grouping, aggregate// state, post-aggregation wrappers, ordering, and pagination reuse the// general path's exact kernels (aggState, sortRowsByOrder, paginate), so// the fused result is identical by construction. ANY expression the// classifier cannot express over typed columns declines the whole chain// back to the general pipeline. Classification is shape-generic: it// never asks what query a chain came from, only whether its expressions// fit the typed vocabulary.
package execimport (chickpeas)// disableColAgg pins differential tests to the general path;// colAggFired counts successful fusions so tests can assert the fused// arm actually took the fused path (a silent double-general run would// pass any differential vacuously).var ( disableColAgg = false colAggFired int)// colAggMaxKeys is the fused group key width (a fixed-size comparable map// key); wider groupings keep the general path.const colAggMaxKeys = 4// colAggKeyKind says how a computed key's int64 unboxes for output.type colAggKeyKind uint8const ( cakInt colAggKeyKind = iota cakBool)// colKeyFn is a typed per-candidate key: ok=false is a null key (its own// group), mirroring the boxed evaluation's Null.type colKeyFn func(id uint32) (int64, bool)// colEntry is one resolved column name flowing through the chain.type colEntry struct { isV bool fn colKeyFn kind colAggKeyKind carried bool val value.Value// carried entries: the (single input row) value}// colEnv resolves column names during the chain walk. synthSlots/synthRow// let chain expressions compile against carried values and the scanned// candidate (slot 0) through the ordinary compile machinery.type colEnv struct { entries map[string]colEntry synthSlots map[string]int synthRow []value.Value ctx *eval.Ctx g *chickpeas.Snapshot}// tryColumnarAggChain fuses the chain headed at segments[i], reporting// (output rows, segments consumed, true) or ok=false with nothing// consumed. Exactness domain: exactly one input row (with more, carried// group keys would participate in cross-row grouping).func tryColumnarAggChain( *eval.Ctx, []*plan.Segment, int, [][]value.Value) ([][]value.Value, int, bool) {ifdisableColAgg || len() != 1 {returnnil, 0, false } := plan.ColAggChainLen(, )if == 0 {returnnil, 0, false } , := .G.(graph.Native)if ! {returnnil, 0, false } := .Snapshot() := [] := .Stages[0].(*plan.MatchStage) := &.Ops[0] := .Slot := ""for , := range .Slots {if == { = } }if == "" {returnnil, 0, false }// Head-segment scratch row: the seeded input, for carried references // in filters and carried column evaluation. := make([]value.Value, .RowWidth)copy(, [0]) := func( int) bool { return != && >= 0 && < len() } := func(int) bool { returnfalse } .MatchEpoch++// Filters: every WHERE conjunct of the scan stage must specialize -- // i64-column-vs-constant conjuncts to bulk IntSweeps (one column // dispatch per chunk), everything else to a per-candidate predicate.var []compile.IntSweepvar []compile.CandPredif .Where != nil {var []ast.Exprplan.SplitAnd(.Where, &)for , := range { := compile.HoistCarriedIn(compile.HoistConstIn(, compile.New(, , .Slots, ), , , .Slots), )if , := compile.CandidateIntSweep(, ); { = append(, )continue } , := compile.CandidatePred(, , .Slots)if ! {returnnil, 0, false } = append(, ) } }// The chain env starts from the head's input columns (carried) and // the scanned var, then each boundary re-maps it through its Returns. := &colEnv{entries: map[string]colEntry{}, synthSlots: map[string]int{}, ctx: , g: } .synthSlots[] = 0 .synthRow = append(.synthRow, value.Value{}) .entries[] = colEntry{isV: true}for , := range .Slots {if == {continue }varvalue.Valueif >= 0 && < len([0]) { = [0][] } .addCarried(, ) }// Boundaries: the head's projection and each interior pass-through // re-map the env; the aggregated boundary itself is consumed by the // key/argument classification below, never re-mapped (its returns // are the aggregate outputs). A single-segment chain applies no // boundary at all -- its keys and arguments resolve over the head's // own inputs.for := ; < +-1; ++ {if !.applyBoundary(&[].Proj) {returnnil, 0, false } } := [+-1] := &.Proj// Group keys and aggregate arguments resolve against the final env.var []colEntry := 0for , := range .GroupIdx { , := .resolveExpr(.Returns[].Expr)if ! {returnnil, 0, false }if .isV {returnnil, 0, false } = append(, )if !.carried { ++ } }if > colAggMaxKeys {returnnil, 0, false }var []struct {func( uint32) value.Valuebool }for , := range .Aggs {switch arg := .Arg.(type) {casenil: = append(, struct {func( uint32) value.Valuebool }{: true})case *ast.Var: , := .entries[.Name]if ! || !.isV {returnnil, 0, false } = append(, struct {func( uint32) value.Valuebool }{: func( uint32) value.Value { returnvalue.Node(graph.NodeID()) }})case *ast.Prop: , := .entries[.Var]if ! || !.isV {returnnil, 0, false } , := colAggPropValue(, .Key)if ! {returnnil, 0, false } = append(, struct {func( uint32) value.Valuebool }{: })default:returnnil, 0, false } }// The fused pass: filter, key, accumulate. , := .NodesWithLabel(.Source.Label)typestruct { [colAggMaxKeys]int64uint8 } := map[]int{}var []aggStatevar [] := func( uint32) {for , := range {if !(, , graph.NodeID()) {return } }var := 0for , := range {if .carried {continue } , := .fn()if { .[] = } else { . |= 1 << } ++ }// Small aggregates (the common colagg shape) probe the key list // linearly -- comparing a few comparable structs beats hashing a // 32-byte key per row; past the threshold the map takes over, // seeded with the linear era's entries.const = 16 , := -1, falseiflen() <= {for := range {if [] == { , = , truebreak } } } else { , = [] }if ! { = len() = append(, )iflen() == +1 && len() == 0 {for := range { [[]] = } } elseiflen() > { [] = }for , := range .Aggs { = append(, aggState{kind: .Kind}) } } := [*len(.Aggs) : (+1)*len(.Aggs)]for , := range {if . { [].update(value.Value{}, false) } else { [].update(.(), true) } } }if {// Chunked candidate walk: sweeps bulk-filter each id chunk (one // column dispatch per conjunct per chunk), then survivors run // the per-candidate predicates and accumulate.const = 4096 := make([]int64, ) := make([]bool, ) := make([]bool, ) := func( []uint32) {for := range { [] = true }for , := range { (, [:len()], [:len()], [:len()]) }for , := range {if [] { () } } }// A selective range conjunct flips the enumeration: instead of // every labeled node testing every predicate, walk the range // index's window (exact count, no estimate) and test label // membership -- chosen only when the window is decisively smaller // than the label, so broad ranges keep the cache-linear label // sweep. Every predicate still runs per candidate (the window is // a superset reduction), so results are identical either way.if := colAggRangeWindow(, .Where, , ); != nil && len() < .Len()/4 { := .LabelDense(.Source.Label) := make([]uint32, 0, ) := func() {iflen() > 0 { () = [:0] } }for , := range { := falseif != nil { := int() >> 6 = < len() && []>>(&63)&1 == 1 } else { = .Contains() }if {if = append(, ); len() == { () } } } () } else { := make([]uint32, ) .IterChunks(, func( []uint32) bool { ()returntrue }) } }// No candidate survived. A keyless aggregate over no input still // emits one zeroed group, exactly like the general accumulator. An // OPTIONAL scan instead emits its null-fill row THROUGH the // aggregator: star aggregates count that row (count(*) reads 1), // value aggregates read null from it (their identity), computed // group keys take the null key, carried keys keep their carried // values. Established against the general path's own output -- // grouped plain MATCH stays zero rows, everything else fills.iflen() == 0 && (len(.GroupIdx) == 0 || .Optional) { := {}if .Optional {for := 0; < ; ++ { . |= 1 << } } = append(, )for , := range .Aggs { = append(, aggState{kind: .Kind}) }if .Optional {for , := range {if . { [].update(value.Value{}, false) } else { [].update(value.Null(), true) } } } }// Output assembly mirrors aggregator.finalize: keys and aggregate // finals into a hidden-slot-extended row, post wrappers, truncate, // order, paginate. := len(.Returns) := make(map[string]int, .NHidden+len(.GroupIdx))for := 0; < .NHidden; ++ { [hiddenAggName()] = + }for , := range .GroupIdx {if , := [.Returns[].Name]; ! { [.Returns[].Name] = } } := make([]RowEval, len(.Post))for , := range .Post { [] = compileEval(, .Expr, ) } := make([][]value.Value, 0, len())for , := range { := make([]value.Value, +.NHidden) := 0for , := range .GroupIdx { := []if .carried { [] = .valcontinue }switch {case .&(1<<) != 0: [] = value.Null()case .kind == cakBool: [] = value.Bool(.[] != 0)default: [] = value.Int(.[]) } ++ } := [*len(.Aggs) : (+1)*len(.Aggs)]for := range .Aggs { [.Aggs[].OutIdx] = [].finalize() }for , := range .Post { [.Col] = [].Eval(, , ) } = append(, [:]) }iflen(.OrderBy) > 0 { = sortRowsByOrder(, , .Slots, func(int) []value.Value { returnnil }, 0, ) } = paginate(, .Skip, .Limit)if .PostWhere != nil {applyPostWhere(, , &) }colAggFired++return , , 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.