package exec
import (
"cmp"
"maps"
"math"
"slices"
"strconv"
"github.com/freeeve/gochickpeas/gql/internal/eval"
"github.com/freeeve/gochickpeas/gql/internal/plan"
"github.com/freeeve/gochickpeas/gql/value"
)
var (
aggTopkBuilds int
disableAggTopk bool
)
func (a *aggregator ) update (ctx *eval .Ctx , m []value .Value , proj *plan .ProjPlan , slots map [string ]int ) {
idx := -1
if a .keySlots != nil {
var gk64 uint64
var packed bool
if len (a .keySlots ) == 1 {
gk64 , packed = packGroupKey1 (m [a .keySlots [0 ]])
} else {
gk64 , packed = packGroupKey2 (m [a .keySlots [0 ]], m [a .keySlots [1 ]])
}
if packed {
idx = a .indexI .GetOrCreate (gk64 , func () int {
a .keyScratch = a .keyScratch [:0 ]
for _ , s := range a .keySlots {
a .keyScratch = append (a .keyScratch , m [s ])
}
return a .appendGroup (a .keyScratch )
})
}
}
if idx < 0 {
a .keyScratch = a .keyScratch [:0 ]
for _ , c := range a .groupC {
a .keyScratch = append (a .keyScratch , c .Eval (ctx , m , slots ))
}
idx = a .groupIdx (a .keyScratch )
}
states := a .statesOf (idx )
var seen []distinctSet
if a .hasDistinct {
seen = a .seenOf (idx )
}
var mm []value .Value
if a .hasMinMax {
mm = a .mmOf (idx )
}
var items [][]value .Value
if a .hasCollect {
items = a .itemsOf (idx )
}
for j := range proj .Aggs {
var arg value .Value
present := a .aggC [j ] != nil
if present {
if s := a .argSlots [j ]; s >= 0 {
arg = m [s ]
} else {
arg = a .aggC [j ].Eval (ctx , m , slots )
}
}
if proj .Aggs [j ].Distinct && present && !arg .IsNull () {
if !seen [j ].add (arg , &a .dkScratch ) {
continue
}
}
switch a .kinds [j ] {
case plan .AggMin , plan .AggMax :
if arg .IsNull () {
continue
}
if mm [j ].IsNull () {
mm [j ] = arg
} else if c , ok := value .Compare (arg , mm [j ]); ok &&
((a .kinds [j ] == plan .AggMin && c < 0 ) || (a .kinds [j ] == plan .AggMax && c > 0 )) {
mm [j ] = arg
}
case plan .AggCollect :
if !arg .IsNull () {
items [j ] = append (items [j ], arg )
}
case plan .AggPercentileCont , plan .AggPercentileDisc :
if _ , ok := arg .AsFloat (); ok {
items [j ] = append (items [j ], arg )
}
default :
states [j ].update (arg , present )
}
}
}
func (a *aggregator ) postCompile (ctx *eval .Ctx , proj *plan .ProjPlan ) (map [string ]int , []RowEval ) {
nCols := len (proj .Returns )
postSlots := make (map [string ]int , proj .NHidden +len (proj .GroupIdx ))
for k := 0 ; k < proj .NHidden ; k ++ {
postSlots [hiddenAggName (k )] = nCols + k
}
for _ , gi := range proj .GroupIdx {
if _ , ok := postSlots [proj .Returns [gi ].Name ]; !ok {
postSlots [proj .Returns [gi ].Name ] = gi
}
}
postC := make ([]RowEval , len (proj .Post ))
for i , p := range proj .Post {
postC [i ] = compileEval (ctx , p .Expr , postSlots )
}
return postSlots , postC
}
func (a *aggregator ) forEachGroup (ctx *eval .Ctx , proj *plan .ProjPlan , fn func (row []value .Value )) {
if a .nGroups == 0 && len (proj .GroupIdx ) == 0 {
a .appendGroup (nil )
}
nCols := len (proj .Returns )
postSlots , postC := a .postCompile (ctx , proj )
stride := nCols + proj .NHidden
scratch := make ([]value .Value , stride )
for idx := 0 ; idx < a .nGroups ; idx ++ {
clear (scratch )
a .emitGroup (ctx , proj , idx , scratch , postC , postSlots )
fn (scratch [:nCols ])
}
}
func (a *aggregator ) finalize (ctx *eval .Ctx , proj *plan .ProjPlan , slots map [string ]int ) [][]value .Value {
if a .nGroups == 0 && len (proj .GroupIdx ) == 0 {
a .appendGroup (nil )
}
nCols := len (proj .Returns )
postSlots , postC := a .postCompile (ctx , proj )
stride := nCols + proj .NHidden
if bound := orderBound (proj ); bound >= 0 && bound < a .nGroups && len (proj .OrderBy ) > 0 && !proj .Distinct && !disableAggTopk {
return a .finalizeTopK (ctx , proj , slots , postC , postSlots , nCols , stride , bound )
}
arena := make ([]value .Value , a .nGroups *stride )
out := make ([][]value .Value , 0 , a .nGroups )
for idx := 0 ; idx < a .nGroups ; idx ++ {
row := arena [idx *stride : idx *stride +stride : idx *stride +stride ]
a .emitGroup (ctx , proj , idx , row , postC , postSlots )
out = append (out , row [:nCols ])
}
if len (proj .OrderBy ) > 0 {
out = sortRowsByOrder (ctx , proj , slots , func (int ) []value .Value { return nil }, 0 , out )
}
return paginate (out , proj .Skip , proj .Limit )
}
func (a *aggregator ) emitGroup (ctx *eval .Ctx , proj *plan .ProjPlan , idx int , row []value .Value , postC []RowEval , postSlots map [string ]int ) {
keys := a .keysOf (idx )
for k , gi := range proj .GroupIdx {
row [gi ] = keys [k ]
}
states := a .statesOf (idx )
var mm []value .Value
if a .hasMinMax {
mm = a .mmOf (idx )
}
var items [][]value .Value
if a .hasCollect {
items = a .itemsOf (idx )
}
for j := range proj .Aggs {
switch a .kinds [j ] {
case plan .AggMin , plan .AggMax :
row [proj .Aggs [j ].OutIdx ] = mm [j ]
case plan .AggCollect :
row [proj .Aggs [j ].OutIdx ] = value .List (items [j ])
case plan .AggPercentileCont , plan .AggPercentileDisc :
row [proj .Aggs [j ].OutIdx ] = percentileOf (ctx , a .pctC [j ], items [j ], a .kinds [j ] == plan .AggPercentileCont )
default :
row [proj .Aggs [j ].OutIdx ] = states [j ].finalize ()
}
}
for i , p := range proj .Post {
row [p .Col ] = postC [i ].Eval (ctx , row , postSlots )
}
}
func (a *aggregator ) finalizeTopK (ctx *eval .Ctx , proj *plan .ProjPlan , slots map [string ]int , postC []RowEval , postSlots map [string ]int , nCols , stride , bound int ) [][]value .Value {
nk := len (proj .OrderBy )
colIdx := make ([]int , nk )
for k := range proj .OrderBy {
colIdx [k ] = plan .OrderColIndex (proj .OrderBy [k ].Expr , proj .Columns , proj .Returns )
}
scope := make (map [string ]int , len (slots )+len (proj .Columns ))
maps .Copy (scope , slots )
for i , c := range proj .Columns {
scope [c ] = i
}
topk := newTopKRows (bound , nk , proj .OrderBy )
scratch := make ([]value .Value , stride )
kbuf := make ([]value .Value , nk )
arena := rowArena {width : nCols , chunkValues : min (arenaChunkValues , max (64 , 2 *bound )*nCols )}
for idx := 0 ; idx < a .nGroups ; idx ++ {
clear (scratch )
a .emitGroup (ctx , proj , idx , scratch , postC , postSlots )
for k := range proj .OrderBy {
if ci := colIdx [k ]; ci >= 0 {
kbuf [k ] = scratch [ci ]
} else {
kbuf [k ] = eval .Eval (ctx , proj .OrderBy [k ].Expr , scratch [:nCols ], scope )
}
}
if !topk .wouldAccept (kbuf ) {
topk .seq ++
continue
}
aggTopkBuilds ++
out := arena .alloc ()
copy (out , scratch [:nCols ])
if !topk .offer (kbuf , out ) {
arena .rollback ()
}
}
return paginate (topk .sorted (), proj .Skip , proj .Limit )
}
func hiddenAggName(k int ) string {
return "__agg" + strconv .Itoa (k )
}
type aggSink struct {
ctx *eval .Ctx
agg *aggregator
proj *plan .ProjPlan
slots map [string ]int
}
func newAggSink(ctx *eval .Ctx , proj *plan .ProjPlan , slots map [string ]int ) *aggSink {
return &aggSink {ctx : ctx , agg : newAggregator (ctx , proj , slots ), proj : proj , slots : slots }
}
func (a *aggSink ) push (row []value .Value ) bool {
a .agg .update (a .ctx , row , a .proj , a .slots )
return true
}
func (a *aggSink ) close () {}
func (a *aggSink ) finalize () [][]value .Value {
return a .agg .finalize (a .ctx , a .proj , a .slots )
}
func percentileOf(ctx *eval .Ctx , pc RowEval , vals []value .Value , cont bool ) value .Value {
if pc == nil || len (vals ) == 0 {
return value .Null ()
}
p , ok := pc .Eval (ctx , nil , nil ).AsFloat ()
if !ok || p < 0 || p > 1 {
return value .Null ()
}
slices .SortStableFunc (vals , func (a , b value .Value ) int {
af , _ := a .AsFloat ()
bf , _ := b .AsFloat ()
return cmp .Compare (af , bf )
})
n := len (vals )
if !cont {
idx := int (math .Ceil (p * float64 (n )))
if idx < 1 {
idx = 1
}
return vals [idx -1 ]
}
rank := p * float64 (n -1 )
lo := int (math .Floor (rank ))
hi := int (math .Ceil (rank ))
lov , _ := vals [lo ].AsFloat ()
hiv , _ := vals [hi ].AsFloat ()
return value .Float (lov + (hiv -lov )*(rank -float64 (lo )))
}
The pages are generated with Golds v0.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 .