1use crate::physical::common::{hash_value_into, store_value_in_vector};
3use crate::physical::types::OperatorResult;
4use akar_common::types::{PhysicalTypeID, Value};
5use akar_common::vector::{DataChunk, ValueVector};
6use akar_function::AggregateFunction;
7use akar_function::aggregate::AggValueState;
8use akar_parser::ast::Expression;
9use arrow::compute;
10use arrow::datatypes::{Float64Type, Int32Type, Int64Type};
11
12pub struct AggregateHashTable {
19 funcs: Vec<AggregateFunction>,
21 group_by_cols: Vec<u32>,
23 agg_expressions: Vec<Vec<Expression>>,
25}
26
27impl AggregateHashTable {
28 pub fn new(funcs: Vec<AggregateFunction>, group_by_cols: Vec<u32>, agg_expressions: Vec<Vec<Expression>>) -> Self {
29 Self {
30 funcs,
31 group_by_cols,
32 agg_expressions,
33 }
34 }
35
36 pub fn aggregate(&self, chunks: &[DataChunk]) -> OperatorResult {
38 let total_rows: usize = chunks.iter().map(|c| c.size).sum();
39
40 let mut col_indices = resolve_agg_col_indices(
42 &self.agg_expressions,
43 chunks.first().map(|c| c.field_names.as_slice()).unwrap_or(&[]),
44 );
45
46 if col_indices.is_empty() && !self.funcs.is_empty() {
50 col_indices = vec![Some(0); self.funcs.len()];
51 }
52
53 if total_rows == 0 && self.group_by_cols.is_empty() {
54 let mut fields = Vec::new();
57 for func in &self.funcs {
58 let state = AggValueState::new(func);
59 let result = state.finalize();
60 let phys_type = result.physical_type();
61 let mut v = ValueVector::new(phys_type, 1);
62 v.resize(1);
63 store_value_in_vector(&mut v, 0, &result)?;
64 fields.push(v);
65 }
66 return Ok(vec![{
67 let arrow_fields = fields
68 .iter()
69 .map(|v| akar_common::arrow_vector::ArrowVector::from_legacy(v).array)
70 .collect::<Vec<_>>();
71 let arrow_field_types = fields.iter().map(|v| v.physical_type()).collect::<Vec<_>>();
72 DataChunk::new(arrow_fields, arrow_field_types)
73 }]);
74 }
75
76 if total_rows == 0 {
77 return self.empty_result();
78 }
79
80 if self.group_by_cols.is_empty() {
82 let all_count = self
83 .funcs
84 .iter()
85 .all(|f| matches!(f, AggregateFunction::Count | AggregateFunction::CountStar));
86 if all_count {
87 let mut fields = Vec::new();
88 for (i, func) in self.funcs.iter().enumerate() {
89 let mut total = 0u64;
90 for chunk in chunks {
91 match func {
92 AggregateFunction::CountStar => {
93 total += chunk.active_rows() as u64;
94 }
95 AggregateFunction::Count => {
96 if let Some(col_idx) = col_indices[i] {
97 if let Some(field) = chunk.fields.get(col_idx) {
98 if chunk.sel_vector.is_some() {
99 for row in chunk.iter_rows() {
100 if !field.is_null(row) {
101 total += 1;
102 }
103 }
104 } else {
105 total += (field.len() - field.null_count()) as u64;
106 }
107 }
108 } else {
109 total += chunk.active_rows() as u64;
110 }
111 }
112 _ => {}
113 }
114 }
115 let state = AggValueState::Count(total);
116 let result = state.finalize();
117 let phys_type = result.physical_type();
118 let mut v = ValueVector::new(phys_type, 1);
119 v.resize(1);
120 store_value_in_vector(&mut v, 0, &result)?;
121 fields.push(v);
122 }
123 return Ok(vec![{
124 let arrow_fields = fields
125 .iter()
126 .map(|v| akar_common::arrow_vector::ArrowVector::from_legacy(v).array)
127 .collect::<Vec<_>>();
128 let arrow_field_types = fields.iter().map(|v| v.physical_type()).collect::<Vec<_>>();
129 DataChunk::new(arrow_fields, arrow_field_types)
130 }]);
131 }
132 }
133
134 if self.group_by_cols.is_empty() && chunks.iter().all(|c| c.sel_vector.is_none()) {
137 let all_scalar_agg = self.funcs.iter().all(|f| {
138 matches!(
139 f,
140 AggregateFunction::Sum | AggregateFunction::Min | AggregateFunction::Max | AggregateFunction::Avg
141 )
142 });
143 if all_scalar_agg {
144 let mut fields = Vec::new();
145 for (i, func) in self.funcs.iter().enumerate() {
146 if let Some(col_idx) = col_indices[i] {
147 let result_val = arrow_scalar_agg(func, chunks, col_idx);
148 let phys_type = result_val.physical_type();
149 let mut v = ValueVector::new(phys_type, 1);
150 v.resize(1);
151 store_value_in_vector(&mut v, 0, &result_val)?;
152 fields.push(v);
153 } else {
154 let mut v = ValueVector::new(PhysicalTypeID::Int64, 1);
155 v.resize(1);
156 v.set_null(0, true);
157 fields.push(v);
158 }
159 }
160 return Ok(vec![DataChunk::new(
161 fields
162 .iter()
163 .map(|v| akar_common::arrow_vector::ArrowVector::from_legacy(v).array)
164 .collect(),
165 fields.iter().map(|v| v.physical_type()).collect(),
166 )]);
167 }
168 }
169
170 if total_rows > 1000 {
172 self.aggregate_parallel(chunks, &col_indices)
173 } else {
174 self.aggregate_sequential(chunks, &col_indices)
175 }
176 }
177
178 fn aggregate_parallel(&self, chunks: &[DataChunk], col_indices: &[Option<usize>]) -> OperatorResult {
180 use rayon::prelude::*;
181 let total_rows: usize = chunks.iter().map(|c| c.size).sum();
182 type LocalTable = hashbrown::HashMap<u64, Vec<(Value, Vec<AggValueState>)>>;
183
184 let group_cols = &self.group_by_cols;
186 let funcs = &self.funcs;
187
188 let results: Vec<LocalTable> = chunks
189 .par_iter()
190 .map(|chunk| {
191 let mut local: LocalTable = hashbrown::HashMap::with_capacity(chunk.size.max(16));
192 for row in chunk.iter_rows() {
193 let hash = hash_group_key(chunk, group_cols, row);
194 let bucket = local.entry(hash).or_default();
195 let entry = bucket.iter_mut().find(|(k, _)| keys_equal(k, chunk, group_cols, row));
196 if let Some((_, states)) = entry {
197 update_states_row(states, chunk, funcs, col_indices, row);
198 } else {
199 let key = build_group_key(chunk, group_cols, row);
200 let mut states = funcs.iter().map(AggValueState::new).collect::<Vec<_>>();
201 update_states_row(&mut states, chunk, funcs, col_indices, row);
202 bucket.push((key, states));
203 }
204 }
205 local
206 })
207 .collect();
208
209 let mut merged: LocalTable = hashbrown::HashMap::with_capacity(total_rows.max(16));
211 for local in results {
212 for (hash, bucket) in local {
213 let mbucket = merged.entry(hash).or_default();
214 for (key, states) in bucket {
215 let entry = mbucket.iter_mut().find(|(k, _)| *k == key);
216 if let Some((_, existing)) = entry {
217 for (i, s) in states.iter().enumerate() {
218 existing[i].merge(s);
219 }
220 } else {
221 mbucket.push((key, states));
222 }
223 }
224 }
225 }
226 self.build_output(&merged)
227 }
228
229 fn aggregate_sequential(&self, chunks: &[DataChunk], col_indices: &[Option<usize>]) -> OperatorResult {
231 let total_rows: usize = chunks.iter().map(|c| c.size).sum();
232 let mut groups: hashbrown::HashMap<u64, Vec<(Value, Vec<AggValueState>)>> =
233 hashbrown::HashMap::with_capacity(total_rows.max(16));
234 let group_cols = &self.group_by_cols;
235 let funcs = &self.funcs;
236
237 for chunk in chunks {
238 for row in chunk.iter_rows() {
239 let hash = hash_group_key(chunk, group_cols, row);
240 let bucket = groups.entry(hash).or_default();
241 let entry = bucket.iter_mut().find(|(k, _)| keys_equal(k, chunk, group_cols, row));
242 if let Some((_, states)) = entry {
243 update_states_row(states, chunk, funcs, col_indices, row);
244 } else {
245 let key = build_group_key(chunk, group_cols, row);
246 let mut states = funcs.iter().map(AggValueState::new).collect::<Vec<_>>();
247 update_states_row(&mut states, chunk, funcs, col_indices, row);
248 bucket.push((key, states));
249 }
250 }
251 }
252 self.build_output(&groups)
253 }
254
255 fn empty_result(&self) -> OperatorResult {
256 let num_cols = self.group_by_cols.len() + self.funcs.len();
257 Ok(vec![DataChunk::new(
258 Vec::with_capacity(num_cols),
259 Vec::with_capacity(num_cols),
260 )])
261 }
262
263 fn empty_scalar_result(&self) -> OperatorResult {
267 let mut fields = Vec::new();
268 for func in &self.funcs {
269 let state = AggValueState::new(func);
270 let result = state.finalize();
271 let phys_type = result.physical_type();
272 let mut v = ValueVector::new(phys_type, 1);
273 v.resize(1);
274 store_value_in_vector(&mut v, 0, &result)?;
275 fields.push(v);
276 }
277 Ok(vec![{
278 let arrow_fields = fields
279 .iter()
280 .map(|v| akar_common::arrow_vector::ArrowVector::from_legacy(v).array)
281 .collect::<Vec<_>>();
282 let arrow_field_types = fields.iter().map(|v| v.physical_type()).collect::<Vec<_>>();
283 DataChunk::new(arrow_fields, arrow_field_types)
284 }])
285 }
286
287 pub fn build_output(&self, groups: &hashbrown::HashMap<u64, Vec<(Value, Vec<AggValueState>)>>) -> OperatorResult {
288 if groups.is_empty() {
289 if self.group_by_cols.is_empty() && !self.funcs.is_empty() {
292 return self.empty_scalar_result();
293 }
294 return self.empty_result();
295 }
296
297 let mut group_keys: Vec<Value> = Vec::new();
299 let mut agg_results: Vec<Vec<Value>> = (0..self.funcs.len()).map(|_| Vec::new()).collect();
300
301 for bucket in groups.values() {
302 for (key, states) in bucket {
303 group_keys.push(key.clone());
304 for (i, state) in states.iter().enumerate() {
305 agg_results[i].push(state.finalize());
306 }
307 }
308 }
309
310 let num_group_cols = self.group_by_cols.len();
311 let num_rows = group_keys.len();
312 let mut output = Vec::with_capacity(num_group_cols + self.funcs.len());
313
314 if num_group_cols == 1 {
316 let first_val = &group_keys[0];
317 let phys_type = first_val.physical_type();
318 let mut v = ValueVector::new(phys_type, num_rows);
319 v.resize(num_rows);
320 for (row, key) in group_keys.iter().enumerate() {
321 if matches!(key, Value::Null) {
322 v.set_null(row, true);
323 } else {
324 store_value_in_vector(&mut v, row, key)?;
325 }
326 }
327 output.push(v);
328 } else {
329 for gc_idx in 0..num_group_cols {
330 let first_key = &group_keys[0];
331 let inner_val = match first_key {
332 Value::List(vals) => vals.get(gc_idx).cloned().unwrap_or(Value::Null),
333 _ => Value::Null,
334 };
335 let phys_type = inner_val.physical_type();
336 let mut v = ValueVector::new(phys_type, num_rows);
337 v.resize(num_rows);
338 for (row, key) in group_keys.iter().enumerate() {
339 let val = match key {
340 Value::List(vals) => vals.get(gc_idx).cloned().unwrap_or(Value::Null),
341 _ => Value::Null,
342 };
343 if matches!(val, Value::Null) {
344 v.set_null(row, true);
345 } else {
346 store_value_in_vector(&mut v, row, &val)?;
347 }
348 }
349 output.push(v);
350 }
351 }
352
353 for i in 0..self.funcs.len() {
355 let first_val = &agg_results[i][0];
356 let phys_type = first_val.physical_type();
357 let mut v = ValueVector::new(phys_type, num_rows);
358 v.resize(num_rows);
359 for (row, val) in agg_results[i].iter().enumerate() {
360 store_value_in_vector(&mut v, row, val)?;
361 }
362 output.push(v);
363 }
364
365 const CHUNK_SIZE: usize = 2048;
367 let mut chunks = Vec::new();
368
369 for chunk_start in (0..num_rows).step_by(CHUNK_SIZE) {
370 let chunk_end = (chunk_start + CHUNK_SIZE).min(num_rows);
371 let chunk_len = chunk_end - chunk_start;
372
373 let mut chunk_fields = Vec::with_capacity(output.len());
374 for field in &output {
375 let mut new_v = ValueVector::new(field.physical_type(), chunk_len);
376 new_v.resize(chunk_len);
377 for i in 0..chunk_len {
378 if field.is_null(chunk_start + i) {
379 new_v.set_null(i, true);
380 } else if let Some(val) = field.get_value(chunk_start + i) {
381 store_value_in_vector(&mut new_v, i, &val)?;
382 }
383 }
384 chunk_fields.push(new_v);
385 }
386 chunks.push({
387 let arrow_fields = chunk_fields
388 .iter()
389 .map(|v| akar_common::arrow_vector::ArrowVector::from_legacy(v).array)
390 .collect::<Vec<_>>();
391 let arrow_field_types = chunk_fields.iter().map(|v| v.physical_type()).collect::<Vec<_>>();
392 DataChunk::new(arrow_fields, arrow_field_types)
393 });
394 }
395
396 Ok(chunks)
397 }
398}
399
400pub fn hash_group_key(chunk: &DataChunk, group_cols: &[u32], row: usize) -> u64 {
404 use std::hash::Hash;
405 use std::hash::Hasher;
406 let mut hasher = ahash::AHasher::default();
407 for &gc in group_cols {
408 let col = gc as usize;
409 if col >= chunk.fields.len() {
410 0u8.hash(&mut hasher);
411 continue;
412 }
413 if chunk.is_null(col, row) {
414 0u8.hash(&mut hasher);
415 continue;
416 }
417 match chunk.field_types[col] {
418 PhysicalTypeID::Int64 => {
419 let v = chunk.get_i64(col, row).unwrap_or(0);
420 v.hash(&mut hasher);
421 }
422 PhysicalTypeID::Int32 => {
423 let v = chunk.get_i32(col, row).unwrap_or(0);
424 v.hash(&mut hasher);
425 }
426 PhysicalTypeID::Double => {
427 let v = chunk.get_f64(col, row).unwrap_or(0.0);
428 v.to_bits().hash(&mut hasher);
429 }
430 PhysicalTypeID::Bool => {
431 let v = chunk.get_bool(col, row).unwrap_or(false);
432 v.hash(&mut hasher);
433 }
434 PhysicalTypeID::String => {
435 if let Some(s) = chunk.get_string(col, row) {
436 s.hash(&mut hasher);
437 }
438 }
439 _ => {
440 if let Some(val) = chunk.get_value(col, row) {
442 hash_value_into(&val, &mut hasher);
443 }
444 }
445 }
446 }
447 hasher.finish()
448}
449
450pub fn keys_equal(stored: &Value, chunk: &DataChunk, group_cols: &[u32], row: usize) -> bool {
453 if group_cols.is_empty() {
454 return true;
459 }
460 if group_cols.len() == 1 {
461 let col = group_cols[0] as usize;
462 if col >= chunk.fields.len() {
463 return *stored == Value::Null;
464 }
465 if chunk.is_null(col, row) {
466 return *stored == Value::Null;
467 }
468 return match stored {
469 Value::Int64(v) => chunk.get_i64(col, row).is_some_and(|x| x == *v),
470 Value::Int32(v) => chunk.get_i32(col, row).is_some_and(|x| x == *v),
471 Value::Double(v) => chunk.get_f64(col, row).is_some_and(|x| x == *v),
472 Value::Bool(v) => chunk.get_bool(col, row).is_some_and(|x| x == *v),
473 Value::String(v) => chunk.get_string(col, row).is_some_and(|x| x == *v),
474 _ => {
475 let val = chunk.get_value(col, row).unwrap_or(Value::Null);
476 *stored == val
477 }
478 };
479 }
480 match stored {
481 Value::List(vals) if vals.len() == group_cols.len() => vals.iter().enumerate().all(|(i, v)| {
482 let col = group_cols[i] as usize;
483 if col >= chunk.fields.len() {
484 return *v == Value::Null;
485 }
486 if chunk.is_null(col, row) {
487 return *v == Value::Null;
488 }
489 match v {
490 Value::Int64(expected) => chunk.get_i64(col, row).is_some_and(|x| x == *expected),
491 Value::Int32(expected) => chunk.get_i32(col, row).is_some_and(|x| x == *expected),
492 Value::Double(expected) => chunk.get_f64(col, row).is_some_and(|x| x == *expected),
493 Value::Bool(expected) => chunk.get_bool(col, row).is_some_and(|x| x == *expected),
494 Value::String(expected) => chunk.get_string(col, row).is_some_and(|x| x == *expected),
495 _ => {
496 let val = chunk.get_value(col, row).unwrap_or(Value::Null);
497 *v == val
498 }
499 }
500 }),
501 _ => false,
502 }
503}
504
505pub fn build_group_key(chunk: &DataChunk, group_cols: &[u32], row: usize) -> Value {
507 if group_cols.is_empty() {
508 return Value::Null;
509 }
510 if group_cols.len() == 1 {
511 chunk
512 .fields
513 .get(group_cols[0] as usize)
514 .map(|_| chunk.get_value(group_cols[0] as usize, row))
515 .unwrap_or(Some(Value::Null))
516 .unwrap_or(Value::Null)
517 } else {
518 let vals: Vec<Value> = group_cols
519 .iter()
520 .map(|&gc| {
521 chunk
522 .fields
523 .get(gc as usize)
524 .map(|_| chunk.get_value(gc as usize, row))
525 .unwrap_or(Some(Value::Null))
526 .unwrap_or(Value::Null)
527 })
528 .collect();
529 Value::List(vals)
530 }
531}
532
533fn resolve_name_to_col(name: &str, field_names: &[String]) -> Option<usize> {
536 if let Some(idx) = field_names.iter().position(|n| n == name) {
537 return Some(idx);
538 }
539 if let Ok(idx) = name.parse::<usize>() {
540 if idx < field_names.len() {
541 return Some(idx);
542 }
543 }
544 let dot_name = format!(".{}", name);
545 if let Some(idx) = field_names.iter().position(|n| n.ends_with(&dot_name)) {
546 return Some(idx);
547 }
548 None
549}
550
551pub fn resolve_agg_col_indices(agg_expressions: &[Vec<Expression>], field_names: &[String]) -> Vec<Option<usize>> {
554 agg_expressions
555 .iter()
556 .map(|args| {
557 for expr in args {
558 match expr {
559 Expression::Variable(name) => {
560 if let Some(idx) = resolve_name_to_col(name, field_names) {
561 return Some(idx);
562 }
563 }
564 Expression::PropertyAccess(base, prop) => {
565 if let Expression::Variable(prefix) = base.as_ref() {
566 let qualified = format!("{}.{}", prefix, prop);
568 if let Some(idx) = field_names.iter().position(|n| n == &qualified) {
569 return Some(idx);
570 }
571 let dot_prop = format!(".{}", prop);
573 if let Some(idx) = field_names.iter().position(|n| n.ends_with(&dot_prop)) {
574 return Some(idx);
575 }
576 }
577 }
578 Expression::Star => return None,
579 _ => {}
580 }
581 }
582 None
583 })
584 .collect()
585}
586
587pub fn resolve_group_by_indices(group_by: &[Expression], field_names: &[String]) -> Vec<u32> {
589 group_by
590 .iter()
591 .map(|expr| match expr {
592 Expression::Variable(name) => resolve_name_to_col(name, field_names).unwrap_or(0) as u32,
593 Expression::PropertyAccess(base, prop) => {
594 if let Expression::Variable(prefix) = base.as_ref() {
595 let qualified = format!("{}.{}", prefix, prop);
596 if let Some(idx) = field_names.iter().position(|n| n == &qualified) {
597 return idx as u32;
598 }
599 let dot_prop = format!(".{}", prop);
600 if let Some(idx) = field_names.iter().position(|n| n.ends_with(&dot_prop)) {
601 return idx as u32;
602 }
603 }
604 0
605 }
606 _ => 0,
607 })
608 .collect()
609}
610
611pub fn update_states_row(
613 states: &mut [AggValueState],
614 chunk: &DataChunk,
615 funcs: &[AggregateFunction],
616 col_indices: &[Option<usize>],
617 row: usize,
618) {
619 for (i, state) in states.iter_mut().enumerate() {
620 if matches!(funcs[i], AggregateFunction::CountStar) {
621 if let AggValueState::Count(n) = state {
622 *n += 1;
623 }
624 continue;
625 }
626 if col_indices.get(i).copied().flatten().is_none() && matches!(funcs[i], AggregateFunction::Count) {
627 if let AggValueState::Count(n) = state {
628 *n += 1;
629 }
630 continue;
631 }
632 let col_idx = col_indices
633 .get(i)
634 .copied()
635 .flatten()
636 .unwrap_or_else(|| i.min(chunk.fields.len().saturating_sub(1)));
637 let val = chunk
638 .fields
639 .get(col_idx)
640 .map(|_| chunk.get_value(col_idx, row))
641 .unwrap_or(Some(Value::Null))
642 .unwrap_or(Value::Null);
643 state.update(&val);
644 }
645}
646
647fn arrow_scalar_agg(func: &AggregateFunction, chunks: &[DataChunk], col_idx: usize) -> Value {
650 let all_values: Vec<Value> = chunks
652 .iter()
653 .flat_map(|c| {
654 if col_idx >= c.fields.len() {
655 return Vec::new();
656 }
657 let field = &c.fields[col_idx];
658 let rows = if c.sel_vector.is_some() {
659 c.iter_rows().collect::<Vec<_>>()
660 } else {
661 (0..c.size).collect::<Vec<_>>()
662 };
663 rows.into_iter()
664 .filter_map(|row| {
665 if field.is_null(row) {
666 None
667 } else {
668 c.get_value(col_idx, row)
669 }
670 })
671 .collect::<Vec<_>>()
672 })
673 .collect();
674
675 if all_values.is_empty() {
676 return match func {
677 AggregateFunction::Sum | AggregateFunction::Avg => Value::Null,
678 AggregateFunction::Min | AggregateFunction::Max => Value::Null,
679 _ => Value::Null,
680 };
681 }
682
683 if let Some(arr) = values_to_prim_array(&all_values) {
685 return match func {
686 AggregateFunction::Sum => compute_sum(&arr),
687 AggregateFunction::Min => compute_min(&arr),
688 AggregateFunction::Max => compute_max(&arr),
689 AggregateFunction::Avg => {
690 let sum_val = compute_sum_f64(&arr);
691 let count = non_null_count(chunks, col_idx);
692 if count == 0 {
693 Value::Null
694 } else {
695 Value::Double(sum_val / count as f64)
696 }
697 }
698 _ => Value::Null,
699 };
700 }
701
702 let mut state = AggValueState::new(func);
704 for val in &all_values {
705 state.update(val);
706 }
707 state.finalize()
708}
709
710enum PrimArray {
711 I64(arrow::array::PrimitiveArray<Int64Type>),
712 I32(arrow::array::PrimitiveArray<Int32Type>),
713 F64(arrow::array::PrimitiveArray<Float64Type>),
714}
715
716fn values_to_prim_array(vals: &[Value]) -> Option<PrimArray> {
717 match &vals[0] {
718 Value::Int64(_) => {
719 let mut b = arrow::array::Int64Builder::with_capacity(vals.len());
720 for v in vals {
721 match v {
722 Value::Int64(n) => b.append_value(*n),
723 Value::Null => b.append_null(),
724 _ => return None,
725 }
726 }
727 Some(PrimArray::I64(b.finish()))
728 }
729 Value::Int32(_) => {
730 let mut b = arrow::array::Int32Builder::with_capacity(vals.len());
731 for v in vals {
732 match v {
733 Value::Int32(n) => b.append_value(*n),
734 Value::Null => b.append_null(),
735 _ => return None,
736 }
737 }
738 Some(PrimArray::I32(b.finish()))
739 }
740 Value::Double(_) => {
741 let mut b = arrow::array::Float64Builder::with_capacity(vals.len());
742 for v in vals {
743 match v {
744 Value::Double(n) => b.append_value(*n),
745 Value::Null => b.append_null(),
746 _ => return None,
747 }
748 }
749 Some(PrimArray::F64(b.finish()))
750 }
751 _ => None,
752 }
753}
754
755fn compute_sum(arr: &PrimArray) -> Value {
756 match arr {
757 PrimArray::I64(a) => compute::sum(a).map(Value::Int64).unwrap_or(Value::Null),
758 PrimArray::I32(a) => compute::sum(a).map(|v| Value::Int64(v as i64)).unwrap_or(Value::Null),
759 PrimArray::F64(a) => compute::sum(a).map(Value::Double).unwrap_or(Value::Null),
760 }
761}
762
763fn compute_sum_f64(arr: &PrimArray) -> f64 {
764 match arr {
765 PrimArray::I64(a) => compute::sum::<Int64Type>(a).unwrap_or(0) as f64,
766 PrimArray::I32(a) => compute::sum::<Int32Type>(a).unwrap_or(0) as f64,
767 PrimArray::F64(a) => compute::sum::<Float64Type>(a).unwrap_or(0.0),
768 }
769}
770
771fn compute_min(arr: &PrimArray) -> Value {
772 match arr {
773 PrimArray::I64(a) => compute::min(a).map(Value::Int64).unwrap_or(Value::Null),
774 PrimArray::I32(a) => compute::min(a).map(|v| Value::Int64(v as i64)).unwrap_or(Value::Null),
775 PrimArray::F64(a) => compute::min(a).map(Value::Double).unwrap_or(Value::Null),
776 }
777}
778
779fn compute_max(arr: &PrimArray) -> Value {
780 match arr {
781 PrimArray::I64(a) => compute::max(a).map(Value::Int64).unwrap_or(Value::Null),
782 PrimArray::I32(a) => compute::max(a).map(|v| Value::Int64(v as i64)).unwrap_or(Value::Null),
783 PrimArray::F64(a) => compute::max(a).map(Value::Double).unwrap_or(Value::Null),
784 }
785}
786
787fn non_null_count(chunks: &[DataChunk], col_idx: usize) -> u64 {
788 let mut count = 0u64;
789 for c in chunks {
790 if col_idx >= c.fields.len() {
791 continue;
792 }
793 let field = &c.fields[col_idx];
794 if c.sel_vector.is_some() {
795 for row in c.iter_rows() {
796 if !field.is_null(row) {
797 count += 1;
798 }
799 }
800 } else {
801 count += (field.len() - field.null_count()) as u64;
802 }
803 }
804 count
805}