// Eager aggregation: the accumulator structs and their fold, the two groupers
// (a typed key path for the common shapes, one reusable boxed-key path for the
// rest), and aggregate/aggregate_seq. The pull form takes a BatchSeq, so one
// fold serves both the in-memory batch array and the grace aggregation's spill
// runs -- engine/extagg.mbt is the over-budget caller.
///|
/// One running accumulator per aggregate spec. Mutable state lives in
/// the payload structs so a fold step updates in place — the enum value
/// itself is allocated once per group, not once per step.
priv struct AccSumI {
mut sum : Int64
mut seen : Bool
mut overflowed : Bool
}
///|
priv struct AccSumF {
mut sum : Double
mut seen : Bool
}
///|
priv struct AccAvgF {
mut sum : Double
mut count : Int64
}
///|
priv struct AccCount {
mut count : Int64
}
///|
priv struct AccDistinct {
seen : Map[String, Bool]
}
///|
priv struct AccVar {
mut mean : Double
mut m2 : Double
mut count : Int64
}
///|
priv struct AccMed {
vals : Array[Double]
}
///|
priv struct AccStrAgg {
mut text : String
mut seen : Bool
}
///|
priv struct AccMinMax {
mut cur : @types.Scalar
mut seen : Bool
}
///|
priv struct AccHll {
sketch : @sketches.HllSketch
}
///|
priv struct AccQuant {
digest : @sketches.TDigest
mut bad_input : Bool // a NaN or infinity reached the digest
}
///|
priv enum Acc {
SumI(AccSumI)
SumF(AccSumF)
AvgF(AccAvgF)
CountOf(AccCount)
Distinct(AccDistinct)
VarF(AccVar)
MedV(AccMed)
StrAgg(AccStrAgg)
MinMax(AccMinMax)
Hll(AccHll)
Quant(AccQuant)
}
///|
fn new_accs(aggs : Array[AggSpec]) -> Array[Acc] {
let row : Array[Acc] = []
for spec in aggs {
row.push(
match spec.kind {
Sum =>
match spec.out_type {
@types.Int64 => SumI({ sum: 0L, seen: false, overflowed: false, })
_ => SumF({ sum: 0.0, seen: false, })
}
Avg => AvgF({ sum: 0.0, count: 0L, })
Count | CountStar => CountOf({ count: 0L, })
CountDistinct => Distinct({ seen: Map([]), })
StringAgg => StrAgg({ text: "", seen: false, })
SdSamp | SdPop | VarSamp | VarPop =>
VarF({ mean: 0.0, m2: 0.0, count: 0L, })
Median => MedV({ vals: [], })
Min | Max => MinMax({ cur: @types.Null, seen: false, })
ApproxCountDistinct =>
Hll({ sketch: @sketches.HllSketch::new_default(), })
ApproxQuantile =>
Quant({ digest: @sketches.TDigest::new_default(), bad_input: false, })
},
)
}
row
}
///|
fn as_double(v : @types.Scalar) -> Double? {
match v {
@types.Int32(x) => Some(x.to_double())
@types.Int64(x) => Some(x.to_double())
@types.Float64(x) => Some(x)
_ => None
}
}
///|
/// Fold one input value into the accumulator, in place. `input` is the
/// pre-evaluated aggregate input for one row; Null inputs are skipped
/// (aggregates ignore NULL).
fn acc_step(acc : Acc, spec : AggSpec, input : @types.Scalar) -> Unit {
if spec.kind is CountStar {
match acc {
CountOf(a) => a.count += 1L
_ => ()
}
return
}
match input {
@types.Null => () // aggregates ignore NULL inputs
v =>
match acc {
SumI(a) =>
// checked add: overflow when the operands share a sign that
// the sum does not (int64 sums wrap silently otherwise)
match v {
@types.Int32(x) => {
let b = x.to_int64()
let sum = a.sum + b
if ((a.sum ^ sum) & (b ^ sum)) < 0L {
a.overflowed = true
}
a.sum = sum
a.seen = true
}
@types.Int64(x) => {
let sum = a.sum + x
if ((a.sum ^ sum) & (x ^ sum)) < 0L {
a.overflowed = true
}
a.sum = sum
a.seen = true
}
_ => ()
}
SumF(a) =>
match as_double(v) {
Some(x) => {
a.sum += x
a.seen = true
}
None => ()
}
AvgF(a) =>
match as_double(v) {
Some(x) => {
a.sum += x
a.count += 1L
}
None => ()
}
StrAgg(a) =>
match v {
@types.Str(s) => {
let sep = match spec.sep {
Some(x) => x
None => ""
}
a.text = if a.seen { a.text + sep + s } else { s }
a.seen = true
}
_ => ()
}
CountOf(a) => a.count += 1L
VarF(a) =>
match as_double(v) {
Some(x) => {
// Welford: numerically stable online variance
let c2 = a.count + 1L
let delta = x - a.mean
a.mean += delta / c2.to_double()
a.m2 += delta * (x - a.mean)
a.count = c2
}
None => ()
}
MedV(a) =>
match as_double(v) {
Some(x) => a.vals.push(x)
None => ()
}
Distinct(a) => a.seen[scalar_key(v)] = true
MinMax(a) =>
if !a.seen {
a.cur = v
a.seen = true
} else {
let c = @types.Scalar::compare_nn(v, a.cur)
match spec.kind {
Min => if c < 0 { a.cur = v }
Max => if c > 0 { a.cur = v }
_ => ()
}
}
Hll(a) => hll_add(a.sketch, v)
Quant(a) =>
match as_double(v) {
Some(x) =>
if x.is_nan() || x.is_inf() {
// the digest rejects non-finite values; the fold cannot
// raise, so the finish step reports it for the query
a.bad_input = true
} else {
a.digest.update(x) catch {
_ => a.bad_input = true
}
}
None => ()
}
}
}
}
///|
fn acc_finish(acc : Acc, spec : AggSpec) -> @types.Scalar raise @types.SqlError {
match acc {
SumI(a) =>
if a.overflowed {
raise @types.SqlError::Execute(
"sum() overflows int64; widen the input or filter values",
)
} else if a.seen {
@types.Int64(a.sum)
} else {
@types.Null
}
VarF(a) => {
// samp needs a second observation; pop needs a first
let samp = spec.kind is SdSamp || spec.kind is VarSamp
let ok = if samp { a.count >= 2L } else { a.count >= 1L }
if !ok {
@types.Null
} else {
let denom = if samp {
(a.count - 1L).to_double()
} else {
a.count.to_double()
}
let variance = a.m2 / denom
// the Sd* kinds are the square root; Var* stay as variance
@types.Float64(
if spec.kind is SdSamp || spec.kind is SdPop {
variance.sqrt()
} else {
variance
},
)
}
}
StrAgg(a) => if a.seen { @types.Str(a.text) } else { @types.Null }
MedV(a) => {
if a.vals.length() == 0 {
return @types.Null
}
a.vals.sort()
let n = a.vals.length()
if n % 2 == 1 {
@types.Float64(a.vals[n / 2])
} else {
@types.Float64((a.vals[n / 2 - 1] + a.vals[n / 2]) / 2.0)
}
}
SumF(a) => if a.seen { @types.Float64(a.sum) } else { @types.Null }
AvgF(a) =>
if a.count > 0L {
@types.Float64(a.sum / a.count.to_double())
} else {
@types.Null
}
CountOf(a) => @types.Int64(a.count)
Distinct(a) => @types.Int64(a.seen.length().to_int64())
MinMax(a) => if a.seen { a.cur } else { @types.Null }
Hll(a) => @types.Int64(hll_round(a.sketch.estimate()))
Quant(a) => {
if a.bad_input {
raise @types.SqlError::Execute(
"approx_quantile() requires finite input; the column holds NaN or infinity",
)
}
let q = match spec.q {
Some(q) => q
None =>
raise @types.SqlError::Execute(
"internal: approx_quantile() bound without a quantile",
)
}
if a.digest.count() == 0.0 {
@types.Null
} else {
@types.Float64(
a.digest.quantile(q) catch {
e =>
raise @types.SqlError::Execute(
"approx_quantile(): \{@colenc_types.EncodingError::message(e)}",
)
},
)
}
}
}
}
///|
/// Feed one value to the distinct sketch in the canonical form the exact
/// path's `scalar_key` uses: floats by value (both zeros are one value,
/// every NaN is one value), strings as their UTF-8 bytes, dates as their
/// epoch day.
fn hll_add(sketch : @sketches.HllSketch, v : @types.Scalar) -> Unit {
match v {
@types.Boolean(b) => sketch.add_bool(b)
@types.Int32(x) => sketch.add_int32(x)
@types.Int64(x) => sketch.add_int64(x)
@types.Date(d) => sketch.add_int32(d)
@types.Str(s) => sketch.add_bytes(@utf8.encode(s))
@types.Float64(x) =>
if x.is_nan() {
// one canonical NaN whatever the payload or sign: the exact path
// compares their text form, which is "NaN" for all of them
sketch.add_double(0x7FF8000000000001L.reinterpret_as_double())
} else if x == 0.0 {
sketch.add_double(0.0) // -0.0 and 0.0 print the same
} else {
sketch.add_double(x)
}
@types.Null => ()
}
}
///|
/// Round a cardinality estimate to the nearest integer, never below zero.
fn hll_round(v : Double) -> Int64 {
if v <= 0.0 {
0L
} else {
(v + 0.5).to_int64()
}
}
///|
/// Type-tagged, length-prefixed key text so distinct scalars never
/// collide across groups or join lookups.
fn scalar_key(v : @types.Scalar) -> String {
let sb = StringBuilder()
match v {
@types.Null => sb.write_string("0:")
@types.Boolean(b) => {
sb.write_string("1:")
sb.write_string(if b { "T" } else { "F" })
}
@types.Int32(x) => {
sb.write_string("2:")
sb.write_string(x.to_string())
}
@types.Int64(x) => {
sb.write_string("3:")
sb.write_string(x.to_string())
}
@types.Float64(x) => {
sb.write_string("4:")
sb.write_string(x.to_string())
}
@types.Str(s) => {
sb.write_string("5:")
sb.write_string(s.length().to_string())
sb.write_char(':')
sb.write_string(s)
}
@types.Date(d) => {
sb.write_string("6:")
sb.write_string(d.to_string())
}
}
sb.to_string()
}
///|
fn canonical_key(vals : Array[@types.Scalar]) -> String {
let sb = StringBuilder()
for v in vals {
sb.write_string(scalar_key(v))
sb.write_char('\u{0001}')
}
sb.to_string()
}
///|
/// How the group index maps a row to its group id. Single int and
/// single string keys hash the raw column value — no per-row key
/// building at all (the string key IS the column's string); every other
/// shape shares one reusable key builder and materializes the boxed
/// key row once per group, not once per row. Nulls never enter the
/// maps: the first null-keyed row opens a shared null group.
priv enum Grouper {
GZero // no GROUP BY: exactly one group
GI32(Map[Int64, Int32])
GI64(Map[Int64, Int32])
GDate(Map[Int64, Int32])
GStr(Map[String, Int32])
GText(Map[String, Int32], StringBuilder)
}
///|
/// A numeric aggregate input resolved to its raw typed arrays, so the
/// fold loop skips the boxed Scalar per cell. MinMax (must return the
/// input's own dtype), CountDistinct (needs the boxed key) and
/// non-numeric inputs stay boxed.
priv enum FastInput {
FBoxed(@types.ColumnVector)
FI32(FixedArray[Int32], FixedArray[Bool])
FI64(FixedArray[Int64], FixedArray[Bool])
FF64(FixedArray[Double], FixedArray[Bool])
}
///|
fn fast_input_of(v : @types.ColumnVector, spec : AggSpec) -> FastInput {
let numeric_kind = match spec.kind {
Sum | Avg | Count | SdSamp | SdPop | VarSamp | VarPop | Median => true
_ => false
}
if !numeric_kind {
return FBoxed(v)
}
match v {
@types.I32(d, valid) => FI32(d, valid)
@types.I64(d, valid) => FI64(d, valid)
@types.F64(d, valid) => FF64(d, valid)
_ => FBoxed(v)
}
}
///|
/// Fold one valid numeric cell into the accumulator, given in both
/// int64 and double views. Only the numeric fold kinds reach this —
/// the binder never pairs them with a dtype the views cannot carry.
fn acc_step_num(acc : Acc, as_int : Int64, as_f64 : Double) -> Unit {
match acc {
SumI(a) => {
// checked add, same rule as acc_step
let sum = a.sum + as_int
if ((a.sum ^ sum) & (as_int ^ sum)) < 0L {
a.overflowed = true
}
a.sum = sum
a.seen = true
}
SumF(a) => {
a.sum += as_f64
a.seen = true
}
AvgF(a) => {
a.sum += as_f64
a.count += 1L
}
CountOf(a) => a.count += 1L
VarF(a) => {
let c2 = a.count + 1L
let delta = as_f64 - a.mean
a.mean += delta / c2.to_double()
a.m2 += delta * (as_f64 - a.mean)
a.count = c2
}
MedV(a) => a.vals.push(as_f64)
_ => ()
}
}
///|
/// A pull-based batch source for aggregation. `next` returns `None`
/// exactly once, at the end.
priv struct BatchSeq {
next : () -> @types.DataBatch? raise @types.SqlError
}
///|
/// A sequence over batches the caller already holds.
fn BatchSeq::of_batches(batches : Array[@types.DataBatch]) -> BatchSeq {
let mut i = 0
{
next: fn() {
if i < batches.length() {
let b = batches[i]
i += 1
Some(b)
} else {
None
}
},
}
}
///|
/// A sequence over spill files: each file is decoded when the cursor
/// reaches it and dropped once its batches have been consumed, so a
/// partition made of many runs stays bounded by one run file.
fn BatchSeq::of_runs(store : SpillStore, ids : Array[Int32]) -> BatchSeq {
let mut file = 0
let mut decoded : Array[@types.DataBatch] = []
let mut inner = 0
{
next: fn() -> @types.DataBatch? raise @types.SqlError {
let mut out : @types.DataBatch? = None
let mut done = false
while !done {
if inner < decoded.length() {
out = Some(decoded[inner])
inner += 1
done = true
} else if file < ids.length() {
decoded = @types.DataBatch::from_spill_bytes((store.read)(ids[file]))
inner = 0
file += 1
} else {
done = true
}
}
out
},
}
}
///|
/// Hash aggregation: one output row per distinct group key (in first-
/// seen order). With no GROUP BY the result is exactly one row, even for
/// empty input.
fn aggregate(
batches : Array[@types.DataBatch],
group_exprs : Array[PhysExpr],
group_types : Array[@types.DataType],
aggs : Array[AggSpec],
) -> @types.DataBatch raise @types.SqlError {
aggregate_seq(BatchSeq::of_batches(batches), group_exprs, group_types, aggs)
}
///|
/// The pull form of `aggregate`: the group state is built batch by batch,
/// so a caller that decodes spill runs (grace aggregation) never holds a
/// whole partition in memory — one run file plus the state at most.
fn aggregate_seq(
seq : BatchSeq,
group_exprs : Array[PhysExpr],
group_types : Array[@types.DataType],
aggs : Array[AggSpec],
) -> @types.DataBatch raise @types.SqlError {
if col_agg_plan(group_types, aggs) is Some(kinds) {
return aggregate_cols(seq, group_exprs, group_types, kinds, aggs)
}
let g = group_exprs.length()
let m = aggs.length()
let grouper : Grouper = if g == 0 {
GZero
} else if g == 1 {
match group_types[0] {
@types.Int32 => GI32(Map([]))
@types.Int64 => GI64(Map([]))
@types.Date => GDate(Map([]))
@types.Str => GStr(Map([]))
_ => GText(Map([]), StringBuilder())
}
} else {
GText(Map([]), StringBuilder())
}
let group_keys : Array[Array[@types.Scalar]] = []
let acc_rows : Array[Array[Acc]] = []
let mut null_gi = -1
let mut zero_created = false
while (seq.next)() is Some(batch) {
// evaluate group keys and aggregate inputs once per batch, then the
// row loop reads the materialized columns
let key_cols : Array[@types.ColumnVector] = []
for e in group_exprs {
key_cols.push(eval_vec(e, batch))
}
let input_cols : Array[FastInput] = []
for spec in aggs {
match spec.input {
Some(e) => input_cols.push(fast_input_of(eval_vec(e, batch), spec))
None =>
input_cols.push(FBoxed(@types.ColumnVector::make(@types.Int32, 0)))
}
}
for r in 0..
if zero_created {
0
} else {
zero_created = true
group_keys.push([])
acc_rows.push(new_accs(aggs))
0
}
GI32(map) =>
match key_cols[0] {
@types.I32(d, valid) =>
if valid[r] {
let k = d[r].to_int64()
match map.get(k) {
Some(i) => i
None => {
let i = group_keys.length()
map[k] = i
group_keys.push([@types.Int32(d[r])])
acc_rows.push(new_accs(aggs))
i
}
}
} else {
if null_gi < 0 {
null_gi = group_keys.length()
group_keys.push([@types.Null])
acc_rows.push(new_accs(aggs))
}
null_gi
}
_ =>
raise @types.SqlError::Execute(
"internal: group key dtype drift (int32)",
)
}
GI64(map) =>
match key_cols[0] {
@types.I64(d, valid) =>
if valid[r] {
let k = d[r]
match map.get(k) {
Some(i) => i
None => {
let i = group_keys.length()
map[k] = i
group_keys.push([@types.Int64(d[r])])
acc_rows.push(new_accs(aggs))
i
}
}
} else {
if null_gi < 0 {
null_gi = group_keys.length()
group_keys.push([@types.Null])
acc_rows.push(new_accs(aggs))
}
null_gi
}
_ =>
raise @types.SqlError::Execute(
"internal: group key dtype drift (int64)",
)
}
GDate(map) =>
match key_cols[0] {
@types.DateV(d, valid) =>
if valid[r] {
let k = d[r].to_int64()
match map.get(k) {
Some(i) => i
None => {
let i = group_keys.length()
map[k] = i
group_keys.push([@types.Date(d[r])])
acc_rows.push(new_accs(aggs))
i
}
}
} else {
if null_gi < 0 {
null_gi = group_keys.length()
group_keys.push([@types.Null])
acc_rows.push(new_accs(aggs))
}
null_gi
}
_ =>
raise @types.SqlError::Execute(
"internal: group key dtype drift (date)",
)
}
GStr(map) =>
match key_cols[0] {
@types.StrV(d, valid) =>
if valid[r] {
let k = d[r]
match map.get(k) {
Some(i) => i
None => {
let i = group_keys.length()
map[k] = i
group_keys.push([@types.Str(d[r])])
acc_rows.push(new_accs(aggs))
i
}
}
} else {
if null_gi < 0 {
null_gi = group_keys.length()
group_keys.push([@types.Null])
acc_rows.push(new_accs(aggs))
}
null_gi
}
_ =>
raise @types.SqlError::Execute(
"internal: group key dtype drift (string)",
)
}
GText(map, sb) => {
sb.reset()
// injective per position: null is 'N', strings are
// length-prefixed, the \u{0001} separator ends each component;
// dtypes are fixed per position so no type tags are needed
for c in 0..
if valid[r] {
sb.write_string(d[r].to_string())
} else {
sb.write_char('N')
}
@types.I64(d, valid) =>
if valid[r] {
sb.write_string(d[r].to_string())
} else {
sb.write_char('N')
}
@types.F64(d, valid) =>
if valid[r] {
sb.write_string(d[r].to_string())
} else {
sb.write_char('N')
}
@types.BoolV(d, valid) =>
if valid[r] {
sb.write_string(if d[r] { "T" } else { "F" })
} else {
sb.write_char('N')
}
@types.StrV(d, valid) =>
if valid[r] {
sb.write_string(d[r].length().to_string())
sb.write_char(':')
sb.write_string(d[r])
} else {
sb.write_char('N')
}
@types.DateV(d, valid) =>
if valid[r] {
sb.write_string(d[r].to_string())
} else {
sb.write_char('N')
}
}
sb.write_char('\u{0001}')
}
let key = sb.to_string()
match map.get(key) {
Some(i) => i
None => {
let kv : Array[@types.Scalar] = []
for c in 0..
if valid[r] {
acc_step_num(row[i], d[r].to_int64(), d[r].to_double())
}
FI64(d, valid) =>
if valid[r] {
acc_step_num(row[i], d[r], d[r].to_double())
}
FF64(d, valid) => if valid[r] { acc_step_num(row[i], 0L, d[r]) }
FBoxed(col) => {
// CountStar has no input column: pass a placeholder rather
// than reading its empty vector; acc_step ignores it
let input = if aggs[i].kind is CountStar {
@types.Null
} else {
col.get(r)
}
acc_step(row[i], aggs[i], input)
}
}
}
}
}
if g == 0 && group_keys.length() == 0 {
// a no-GROUP-BY aggregate over empty input still yields one row
group_keys.push([])
acc_rows.push(new_accs(aggs))
}
let n = group_keys.length()
let columns : Array[@types.ColumnVector] = []
for c in 0.. Unit {
match self.kind {
SumInt => {
self.ints.push(0L)
self.flags.push(0)
}
SumFlt => {
self.dbls.push(0.0)
self.flags.push(0)
}
CountVals | CountAll => self.ints.push(0L)
}
}
///|
/// The checked add `acc_step_num` makes: an int64 sum overflows when the
/// operands share a sign that the result does not.
fn ColAgg::add_int(self : ColAgg, gi : Int32, x : Int64) -> Unit {
let sum = self.ints[gi] + x
let mut f = self.flags[gi] | 1
if ((self.ints[gi] ^ sum) & (x ^ sum)) < 0L {
f = f | 2
}
self.ints[gi] = sum
self.flags[gi] = f
}
///|
fn ColAgg::add_flt(self : ColAgg, gi : Int32, x : Double) -> Unit {
self.dbls[gi] = self.dbls[gi] + x
self.flags[gi] = self.flags[gi] | 1
}
///|
fn col_agg_kind(spec : AggSpec) -> ColAggKind? {
match spec.kind {
Sum =>
if spec.input is Some(_) {
match spec.out_type {
@types.Int64 => Some(SumInt)
@types.Float64 => Some(SumFlt)
_ => None
}
} else {
None
}
Count => if spec.input is Some(_) { Some(CountVals) } else { None }
CountStar => Some(CountAll)
_ => None
}
}
///|
/// The column-wise fold applies to one Int32 or Int64 group key, or to a pair of
/// Int32 keys, over aggregates that are all of the kinds above; any other shape
/// keeps the general fold.
fn col_agg_plan(
group_types : Array[@types.DataType],
aggs : Array[AggSpec],
) -> Array[ColAggKind]? {
if aggs.length() == 0 {
return None
}
match group_types.length() {
1 =>
match group_types[0] {
@types.Int32 | @types.Int64 => ()
_ => return None
}
2 =>
match (group_types[0], group_types[1]) {
(@types.Int32, @types.Int32) => ()
_ => return None
}
_ => return None
}
let kinds : Array[ColAggKind] = []
for spec in aggs {
match col_agg_kind(spec) {
Some(k) => kinds.push(k)
None => return None
}
}
Some(kinds)
}
///|
/// Hash aggregation for one integer key, or a pair of Int32 keys, with the group
/// state held column-wise: the keys of each group are slots in `Int64` arrays and
/// every aggregate is one array (two for a sum), so a group costs its map slot
/// and a few bytes rather than a key row, an accumulator row and an object per
/// aggregate. A pair of Int32 keys packs into one `Int64` slot key, which loses
/// nothing; a row with a NULL in either key goes through a small side map keyed
/// by its text, so each NULL pattern is its own group as in the general fold.
/// Group order (first seen), the shared NULL group of a single key, sums in row
/// order and the overflow error are the general fold's.
fn aggregate_cols(
seq : BatchSeq,
group_exprs : Array[PhysExpr],
group_types : Array[@types.DataType],
kinds : Array[ColAggKind],
aggs : Array[AggSpec],
) -> @types.DataBatch raise @types.SqlError {
let m = aggs.length()
let nk = group_exprs.length()
let slots : Map[Int64, Int32] = Map([])
let nullkeys : Map[String, Int32] = Map([])
let ka : Array[Int64] = []
let kb : Array[Int64] = [] // second key, pairs only
let kf : Array[Int32] = [] // pairs only: bit 0 first key NULL, bit 1 second
let cols : Array[ColAgg] = []
for k in kinds {
cols.push({ kind: k, ints: [], dbls: [], flags: [], })
}
let mut null_gi = -1
while (seq.next)() is Some(batch) {
let rows = batch.row_count()
let kds : Array[FixedArray[Int64]] = []
let kvs : Array[FixedArray[Bool]] = []
for e in group_exprs {
let key_col = eval_vec(e, batch)
kvs.push(v_validity(key_col))
kds.push(
match key_col {
@types.I32(d, _) => {
let o : FixedArray[Int64] = FixedArray::make(rows, 0L)
for i in 0.. d
_ =>
raise @types.SqlError::Execute(
"internal: group key dtype drift (int)",
)
},
)
}
let valid = kvs[0]
let kd = kds[0]
let ins : Array[FastInput] = []
for spec in aggs {
match spec.input {
Some(e) => ins.push(fast_input_of(eval_vec(e, batch), spec))
None => ins.push(FBoxed(@types.ColumnVector::make(@types.Int32, 0)))
}
}
for r in 0.. i
None => {
let i = ka.length()
slots[k] = i
ka.push(a)
kb.push(b)
kf.push(0)
for c in cols {
c.open()
}
i
}
}
} else {
let key = (if v0 { a.to_string() } else { "N" }) +
"," +
(if v1 { b.to_string() } else { "N" })
match nullkeys.get(key) {
Some(i) => i
None => {
let i = ka.length()
nullkeys[key] = i
ka.push(if v0 { a } else { 0L })
kb.push(if v1 { b } else { 0L })
kf.push((if v0 { 0 } else { 1 }) | (if v1 { 0 } else { 2 }))
for c in cols {
c.open()
}
i
}
}
}
} else if valid[r] {
let k = kd[r]
match slots.get(k) {
Some(i) => i
None => {
let i = ka.length()
slots[k] = i
ka.push(k)
for c in cols {
c.open()
}
i
}
}
} else {
if null_gi < 0 {
null_gi = ka.length()
ka.push(0L)
for c in cols {
c.open()
}
}
null_gi
}
for i in 0.. c.ints[gi] = c.ints[gi] + 1L
CountVals => {
let present = match ins[i] {
FI32(_, v) => v[r]
FI64(_, v) => v[r]
FF64(_, v) => v[r]
FBoxed(col) => v_validity(col)[r]
}
if present {
c.ints[gi] = c.ints[gi] + 1L
}
}
SumInt =>
match ins[i] {
FI32(d, v) => if v[r] { c.add_int(gi, d[r].to_int64()) }
FI64(d, v) => if v[r] { c.add_int(gi, d[r]) }
FBoxed(col) =>
match col.get(r) {
@types.Int32(x) => c.add_int(gi, x.to_int64())
@types.Int64(x) => c.add_int(gi, x)
_ => ()
}
FF64(_, _) => ()
}
SumFlt =>
match ins[i] {
FI32(d, v) => if v[r] { c.add_flt(gi, d[r].to_double()) }
FI64(d, v) => if v[r] { c.add_flt(gi, d[r].to_double()) }
FF64(d, v) => if v[r] { c.add_flt(gi, d[r]) }
FBoxed(col) =>
match as_double(col.get(r)) {
Some(x) => c.add_flt(gi, x)
None => ()
}
}
}
}
}
}
let n = ka.length()
let columns : Array[@types.ColumnVector] = []
if nk == 2 {
let da : FixedArray[Int32] = FixedArray::make(n, 0)
let db : FixedArray[Int32] = FixedArray::make(n, 0)
let va : FixedArray[Bool] = FixedArray::make(n, true)
let vb : FixedArray[Bool] = FixedArray::make(n, true)
for i in 0..= 0 {
key_valid[null_gi] = false
}
match group_types[0] {
@types.Int32 => {
let d : FixedArray[Int32] = FixedArray::make(n, 0)
for i in 0.. {
let d : FixedArray[Int64] = FixedArray::make(n, 0L)
for i in 0.. {
let d : FixedArray[Int64] = FixedArray::make(n, 0L)
for gi in 0.. {
let d : FixedArray[Double] = FixedArray::make(n, 0.0)
for gi in 0.. {
let d : FixedArray[Int64] = FixedArray::make(n, 0L)
for gi in 0..