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 pub fn build_output(&self, groups: &hashbrown::HashMap<u64, Vec<(Value, Vec<AggValueState>)>>) -> OperatorResult {
264 if groups.is_empty() {
265 return self.empty_result();
266 }
267
268 let mut group_keys: Vec<Value> = Vec::new();
270 let mut agg_results: Vec<Vec<Value>> = (0..self.funcs.len()).map(|_| Vec::new()).collect();
271
272 for bucket in groups.values() {
273 for (key, states) in bucket {
274 group_keys.push(key.clone());
275 for (i, state) in states.iter().enumerate() {
276 agg_results[i].push(state.finalize());
277 }
278 }
279 }
280
281 let num_group_cols = self.group_by_cols.len();
282 let num_rows = group_keys.len();
283 let mut output = Vec::with_capacity(num_group_cols + self.funcs.len());
284
285 if num_group_cols == 1 {
287 let first_val = &group_keys[0];
288 let phys_type = first_val.physical_type();
289 let mut v = ValueVector::new(phys_type, num_rows);
290 v.resize(num_rows);
291 for (row, key) in group_keys.iter().enumerate() {
292 if matches!(key, Value::Null) {
293 v.set_null(row, true);
294 } else {
295 store_value_in_vector(&mut v, row, key)?;
296 }
297 }
298 output.push(v);
299 } else {
300 for gc_idx in 0..num_group_cols {
301 let first_key = &group_keys[0];
302 let inner_val = match first_key {
303 Value::List(vals) => vals.get(gc_idx).cloned().unwrap_or(Value::Null),
304 _ => Value::Null,
305 };
306 let phys_type = inner_val.physical_type();
307 let mut v = ValueVector::new(phys_type, num_rows);
308 v.resize(num_rows);
309 for (row, key) in group_keys.iter().enumerate() {
310 let val = match key {
311 Value::List(vals) => vals.get(gc_idx).cloned().unwrap_or(Value::Null),
312 _ => Value::Null,
313 };
314 if matches!(val, Value::Null) {
315 v.set_null(row, true);
316 } else {
317 store_value_in_vector(&mut v, row, &val)?;
318 }
319 }
320 output.push(v);
321 }
322 }
323
324 for i in 0..self.funcs.len() {
326 let first_val = &agg_results[i][0];
327 let phys_type = first_val.physical_type();
328 let mut v = ValueVector::new(phys_type, num_rows);
329 v.resize(num_rows);
330 for (row, val) in agg_results[i].iter().enumerate() {
331 store_value_in_vector(&mut v, row, val)?;
332 }
333 output.push(v);
334 }
335
336 const CHUNK_SIZE: usize = 2048;
338 let mut chunks = Vec::new();
339
340 for chunk_start in (0..num_rows).step_by(CHUNK_SIZE) {
341 let chunk_end = (chunk_start + CHUNK_SIZE).min(num_rows);
342 let chunk_len = chunk_end - chunk_start;
343
344 let mut chunk_fields = Vec::with_capacity(output.len());
345 for field in &output {
346 let mut new_v = ValueVector::new(field.physical_type(), chunk_len);
347 new_v.resize(chunk_len);
348 for i in 0..chunk_len {
349 if field.is_null(chunk_start + i) {
350 new_v.set_null(i, true);
351 } else if let Some(val) = field.get_value(chunk_start + i) {
352 store_value_in_vector(&mut new_v, i, &val)?;
353 }
354 }
355 chunk_fields.push(new_v);
356 }
357 chunks.push({
358 let arrow_fields = chunk_fields
359 .iter()
360 .map(|v| akar_common::arrow_vector::ArrowVector::from_legacy(v).array)
361 .collect::<Vec<_>>();
362 let arrow_field_types = chunk_fields.iter().map(|v| v.physical_type()).collect::<Vec<_>>();
363 DataChunk::new(arrow_fields, arrow_field_types)
364 });
365 }
366
367 Ok(chunks)
368 }
369}
370
371pub fn hash_group_key(chunk: &DataChunk, group_cols: &[u32], row: usize) -> u64 {
375 use std::hash::Hash;
376 use std::hash::Hasher;
377 let mut hasher = ahash::AHasher::default();
378 for &gc in group_cols {
379 let col = gc as usize;
380 if col >= chunk.fields.len() {
381 0u8.hash(&mut hasher);
382 continue;
383 }
384 if chunk.is_null(col, row) {
385 0u8.hash(&mut hasher);
386 continue;
387 }
388 match chunk.field_types[col] {
389 PhysicalTypeID::Int64 => {
390 let v = chunk.get_i64(col, row).unwrap_or(0);
391 v.hash(&mut hasher);
392 }
393 PhysicalTypeID::Int32 => {
394 let v = chunk.get_i32(col, row).unwrap_or(0);
395 v.hash(&mut hasher);
396 }
397 PhysicalTypeID::Double => {
398 let v = chunk.get_f64(col, row).unwrap_or(0.0);
399 v.to_bits().hash(&mut hasher);
400 }
401 PhysicalTypeID::Bool => {
402 let v = chunk.get_bool(col, row).unwrap_or(false);
403 v.hash(&mut hasher);
404 }
405 PhysicalTypeID::String => {
406 if let Some(s) = chunk.get_string(col, row) {
407 s.hash(&mut hasher);
408 }
409 }
410 _ => {
411 if let Some(val) = chunk.get_value(col, row) {
413 hash_value_into(&val, &mut hasher);
414 }
415 }
416 }
417 }
418 hasher.finish()
419}
420
421pub fn keys_equal(stored: &Value, chunk: &DataChunk, group_cols: &[u32], row: usize) -> bool {
424 if group_cols.len() == 1 {
425 let col = group_cols[0] as usize;
426 if col >= chunk.fields.len() {
427 return *stored == Value::Null;
428 }
429 if chunk.is_null(col, row) {
430 return *stored == Value::Null;
431 }
432 return match stored {
433 Value::Int64(v) => chunk.get_i64(col, row).is_some_and(|x| x == *v),
434 Value::Int32(v) => chunk.get_i32(col, row).is_some_and(|x| x == *v),
435 Value::Double(v) => chunk.get_f64(col, row).is_some_and(|x| x == *v),
436 Value::Bool(v) => chunk.get_bool(col, row).is_some_and(|x| x == *v),
437 Value::String(v) => chunk.get_string(col, row).is_some_and(|x| x == *v),
438 _ => {
439 let val = chunk.get_value(col, row).unwrap_or(Value::Null);
440 *stored == val
441 }
442 };
443 }
444 match stored {
445 Value::List(vals) if vals.len() == group_cols.len() => vals.iter().enumerate().all(|(i, v)| {
446 let col = group_cols[i] as usize;
447 if col >= chunk.fields.len() {
448 return *v == Value::Null;
449 }
450 if chunk.is_null(col, row) {
451 return *v == Value::Null;
452 }
453 match v {
454 Value::Int64(expected) => chunk.get_i64(col, row).is_some_and(|x| x == *expected),
455 Value::Int32(expected) => chunk.get_i32(col, row).is_some_and(|x| x == *expected),
456 Value::Double(expected) => chunk.get_f64(col, row).is_some_and(|x| x == *expected),
457 Value::Bool(expected) => chunk.get_bool(col, row).is_some_and(|x| x == *expected),
458 Value::String(expected) => chunk.get_string(col, row).is_some_and(|x| x == *expected),
459 _ => {
460 let val = chunk.get_value(col, row).unwrap_or(Value::Null);
461 *v == val
462 }
463 }
464 }),
465 _ => false,
466 }
467}
468
469pub fn build_group_key(chunk: &DataChunk, group_cols: &[u32], row: usize) -> Value {
471 if group_cols.is_empty() {
472 return Value::Null;
473 }
474 if group_cols.len() == 1 {
475 chunk
476 .fields
477 .get(group_cols[0] as usize)
478 .map(|_| chunk.get_value(group_cols[0] as usize, row))
479 .unwrap_or(Some(Value::Null))
480 .unwrap_or(Value::Null)
481 } else {
482 let vals: Vec<Value> = group_cols
483 .iter()
484 .map(|&gc| {
485 chunk
486 .fields
487 .get(gc as usize)
488 .map(|_| chunk.get_value(gc as usize, row))
489 .unwrap_or(Some(Value::Null))
490 .unwrap_or(Value::Null)
491 })
492 .collect();
493 Value::List(vals)
494 }
495}
496
497fn resolve_name_to_col(name: &str, field_names: &[String]) -> Option<usize> {
500 if let Some(idx) = field_names.iter().position(|n| n == name) {
501 return Some(idx);
502 }
503 if let Ok(idx) = name.parse::<usize>() {
504 if idx < field_names.len() {
505 return Some(idx);
506 }
507 }
508 let dot_name = format!(".{}", name);
509 if let Some(idx) = field_names.iter().position(|n| n.ends_with(&dot_name)) {
510 return Some(idx);
511 }
512 None
513}
514
515pub fn resolve_agg_col_indices(agg_expressions: &[Vec<Expression>], field_names: &[String]) -> Vec<Option<usize>> {
518 agg_expressions
519 .iter()
520 .map(|args| {
521 for expr in args {
522 match expr {
523 Expression::Variable(name) => {
524 if let Some(idx) = resolve_name_to_col(name, field_names) {
525 return Some(idx);
526 }
527 }
528 Expression::PropertyAccess(base, prop) => {
529 if let Expression::Variable(prefix) = base.as_ref() {
530 let qualified = format!("{}.{}", prefix, prop);
532 if let Some(idx) = field_names.iter().position(|n| n == &qualified) {
533 return Some(idx);
534 }
535 let dot_prop = format!(".{}", prop);
537 if let Some(idx) = field_names.iter().position(|n| n.ends_with(&dot_prop)) {
538 return Some(idx);
539 }
540 }
541 }
542 Expression::Star => return None,
543 _ => {}
544 }
545 }
546 None
547 })
548 .collect()
549}
550
551pub fn resolve_group_by_indices(group_by: &[Expression], field_names: &[String]) -> Vec<u32> {
553 group_by
554 .iter()
555 .map(|expr| match expr {
556 Expression::Variable(name) => resolve_name_to_col(name, field_names).unwrap_or(0) as u32,
557 Expression::PropertyAccess(base, prop) => {
558 if let Expression::Variable(prefix) = base.as_ref() {
559 let qualified = format!("{}.{}", prefix, prop);
560 if let Some(idx) = field_names.iter().position(|n| n == &qualified) {
561 return idx as u32;
562 }
563 let dot_prop = format!(".{}", prop);
564 if let Some(idx) = field_names.iter().position(|n| n.ends_with(&dot_prop)) {
565 return idx as u32;
566 }
567 }
568 0
569 }
570 _ => 0,
571 })
572 .collect()
573}
574
575pub fn update_states_row(
577 states: &mut [AggValueState],
578 chunk: &DataChunk,
579 funcs: &[AggregateFunction],
580 col_indices: &[Option<usize>],
581 row: usize,
582) {
583 for (i, state) in states.iter_mut().enumerate() {
584 if matches!(funcs[i], AggregateFunction::CountStar) {
585 if let AggValueState::Count(n) = state {
586 *n += 1;
587 }
588 continue;
589 }
590 if col_indices.get(i).copied().flatten().is_none() && matches!(funcs[i], AggregateFunction::Count) {
591 if let AggValueState::Count(n) = state {
592 *n += 1;
593 }
594 continue;
595 }
596 let col_idx = col_indices
597 .get(i)
598 .copied()
599 .flatten()
600 .unwrap_or_else(|| i.min(chunk.fields.len().saturating_sub(1)));
601 let val = chunk
602 .fields
603 .get(col_idx)
604 .map(|_| chunk.get_value(col_idx, row))
605 .unwrap_or(Some(Value::Null))
606 .unwrap_or(Value::Null);
607 state.update(&val);
608 }
609}
610
611fn arrow_scalar_agg(func: &AggregateFunction, chunks: &[DataChunk], col_idx: usize) -> Value {
614 let all_values: Vec<Value> = chunks
616 .iter()
617 .flat_map(|c| {
618 if col_idx >= c.fields.len() {
619 return Vec::new();
620 }
621 let field = &c.fields[col_idx];
622 let rows = if c.sel_vector.is_some() {
623 c.iter_rows().collect::<Vec<_>>()
624 } else {
625 (0..c.size).collect::<Vec<_>>()
626 };
627 rows.into_iter()
628 .filter_map(|row| {
629 if field.is_null(row) {
630 None
631 } else {
632 c.get_value(col_idx, row)
633 }
634 })
635 .collect::<Vec<_>>()
636 })
637 .collect();
638
639 if all_values.is_empty() {
640 return match func {
641 AggregateFunction::Sum | AggregateFunction::Avg => Value::Null,
642 AggregateFunction::Min | AggregateFunction::Max => Value::Null,
643 _ => Value::Null,
644 };
645 }
646
647 if let Some(arr) = values_to_prim_array(&all_values) {
649 return match func {
650 AggregateFunction::Sum => compute_sum(&arr),
651 AggregateFunction::Min => compute_min(&arr),
652 AggregateFunction::Max => compute_max(&arr),
653 AggregateFunction::Avg => {
654 let sum_val = compute_sum_f64(&arr);
655 let count = non_null_count(chunks, col_idx);
656 if count == 0 {
657 Value::Null
658 } else {
659 Value::Double(sum_val / count as f64)
660 }
661 }
662 _ => Value::Null,
663 };
664 }
665
666 let mut state = AggValueState::new(func);
668 for val in &all_values {
669 state.update(val);
670 }
671 state.finalize()
672}
673
674enum PrimArray {
675 I64(arrow::array::PrimitiveArray<Int64Type>),
676 I32(arrow::array::PrimitiveArray<Int32Type>),
677 F64(arrow::array::PrimitiveArray<Float64Type>),
678}
679
680fn values_to_prim_array(vals: &[Value]) -> Option<PrimArray> {
681 match &vals[0] {
682 Value::Int64(_) => {
683 let mut b = arrow::array::Int64Builder::with_capacity(vals.len());
684 for v in vals {
685 match v {
686 Value::Int64(n) => b.append_value(*n),
687 Value::Null => b.append_null(),
688 _ => return None,
689 }
690 }
691 Some(PrimArray::I64(b.finish()))
692 }
693 Value::Int32(_) => {
694 let mut b = arrow::array::Int32Builder::with_capacity(vals.len());
695 for v in vals {
696 match v {
697 Value::Int32(n) => b.append_value(*n),
698 Value::Null => b.append_null(),
699 _ => return None,
700 }
701 }
702 Some(PrimArray::I32(b.finish()))
703 }
704 Value::Double(_) => {
705 let mut b = arrow::array::Float64Builder::with_capacity(vals.len());
706 for v in vals {
707 match v {
708 Value::Double(n) => b.append_value(*n),
709 Value::Null => b.append_null(),
710 _ => return None,
711 }
712 }
713 Some(PrimArray::F64(b.finish()))
714 }
715 _ => None,
716 }
717}
718
719fn compute_sum(arr: &PrimArray) -> Value {
720 match arr {
721 PrimArray::I64(a) => compute::sum(a).map(Value::Int64).unwrap_or(Value::Null),
722 PrimArray::I32(a) => compute::sum(a).map(|v| Value::Int64(v as i64)).unwrap_or(Value::Null),
723 PrimArray::F64(a) => compute::sum(a).map(Value::Double).unwrap_or(Value::Null),
724 }
725}
726
727fn compute_sum_f64(arr: &PrimArray) -> f64 {
728 match arr {
729 PrimArray::I64(a) => compute::sum::<Int64Type>(a).unwrap_or(0) as f64,
730 PrimArray::I32(a) => compute::sum::<Int32Type>(a).unwrap_or(0) as f64,
731 PrimArray::F64(a) => compute::sum::<Float64Type>(a).unwrap_or(0.0),
732 }
733}
734
735fn compute_min(arr: &PrimArray) -> Value {
736 match arr {
737 PrimArray::I64(a) => compute::min(a).map(Value::Int64).unwrap_or(Value::Null),
738 PrimArray::I32(a) => compute::min(a).map(|v| Value::Int64(v as i64)).unwrap_or(Value::Null),
739 PrimArray::F64(a) => compute::min(a).map(Value::Double).unwrap_or(Value::Null),
740 }
741}
742
743fn compute_max(arr: &PrimArray) -> Value {
744 match arr {
745 PrimArray::I64(a) => compute::max(a).map(Value::Int64).unwrap_or(Value::Null),
746 PrimArray::I32(a) => compute::max(a).map(|v| Value::Int64(v as i64)).unwrap_or(Value::Null),
747 PrimArray::F64(a) => compute::max(a).map(Value::Double).unwrap_or(Value::Null),
748 }
749}
750
751fn non_null_count(chunks: &[DataChunk], col_idx: usize) -> u64 {
752 let mut count = 0u64;
753 for c in chunks {
754 if col_idx >= c.fields.len() {
755 continue;
756 }
757 let field = &c.fields[col_idx];
758 if c.sel_vector.is_some() {
759 for row in c.iter_rows() {
760 if !field.is_null(row) {
761 count += 1;
762 }
763 }
764 } else {
765 count += (field.len() - field.null_count()) as u64;
766 }
767 }
768 count
769}