1use std::hash::{BuildHasherDefault, Hash, Hasher};
24use std::sync::{Arc, Mutex, RwLock};
25
26use ahash::AHasher;
27use hashbrown::hash_map::RawEntryMut;
28#[cfg(feature = "parallel")]
29use rayon::prelude::*;
30use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
31use radixdb_core::{CompactArc, CompactVec, I64Map, StringMap};
34use radixdb_core::{Error, Result, Row, RowVec, Value, ValueMap, ValueSet};
35use radixdb_functions::aggregate::{numeric::NumericAccumulator, CompiledAggregate};
36use radixdb_functions::{AggregateFunction, AggregateOrderBySpec, FunctionRegistry};
37use radixdb_sql::ast::*;
38use radixdb_storage::mvcc::engine::MVCCEngine;
39use radixdb_storage::traits::{Engine, QueryResult};
40
41use super::compiled_plan::{CompiledCountDistinct, CompiledExecution};
42use super::context::ExecutionContext;
43#[allow(deprecated)]
44use super::expression::CompiledEvaluator;
45use super::expression::{ExpressionEval, RowFilter};
46use super::mutation::host::ActiveTransaction;
47use super::query_classification::QueryClassification;
48use super::result::ExecutorResult;
49use super::utils::build_column_index_map;
50
51pub use super::utils::{expression_contains_aggregate, is_aggregate_function};
53
54mod execute;
55mod finalize;
56mod global;
57mod grouped;
58mod planning;
59mod rollup;
60mod storage;
61mod streaming;
62#[cfg(test)]
63mod tests;
64
65pub trait AggregationHost: Sync {
68 fn aggregation_engine(&self) -> &Arc<MVCCEngine>;
69 fn aggregation_function_registry(&self) -> &FunctionRegistry;
70 fn aggregation_active_transaction(&self) -> &Mutex<Option<ActiveTransaction>>;
71 fn aggregation_process_where_subqueries(
72 &self,
73 expression: &Expression,
74 context: &ExecutionContext,
75 ) -> Result<Expression>;
76 fn aggregation_try_process_select_subqueries(
77 &self,
78 columns: &[Expression],
79 context: &ExecutionContext,
80 ) -> Result<Option<Vec<Expression>>>;
81 fn aggregation_has_correlated_subqueries(&self, expression: &Expression) -> bool;
82 fn aggregation_process_correlated_expression(
83 &self,
84 expression: &Expression,
85 context: &ExecutionContext,
86 ) -> Result<Expression>;
87 fn aggregation_output_column_names(
88 &self,
89 select_expressions: &[Expression],
90 source_columns: &[String],
91 table_alias: Option<&str>,
92 ) -> Vec<String>;
93}
94
95pub struct AggregationExecutor<'a, H: AggregationHost + ?Sized> {
97 host: &'a H,
98}
99
100impl<'a, H: AggregationHost + ?Sized> AggregationExecutor<'a, H> {
101 fn new(host: &'a H) -> Self {
102 Self { host }
103 }
104}
105
106pub trait AggregationExecutorExt: AggregationHost {
108 fn execute_select_with_aggregation(
109 &self,
110 statement: &SelectStatement,
111 context: &ExecutionContext,
112 rows: RowVec,
113 columns: &[String],
114 ) -> Result<Box<dyn QueryResult>> {
115 AggregationExecutor::new(self)
116 .execute_select_with_aggregation(statement, context, rows, columns)
117 }
118
119 fn execute_aggregation_for_window(
120 &self,
121 statement: &SelectStatement,
122 context: &ExecutionContext,
123 rows: &[(i64, Row)],
124 columns: &[String],
125 ) -> Result<(Vec<String>, RowVec)> {
126 AggregationExecutor::new(self)
127 .execute_aggregation_for_window(statement, context, rows, columns)
128 }
129
130 fn try_aggregation_pushdown(
131 &self,
132 table: &dyn radixdb_storage::traits::Table,
133 statement: &SelectStatement,
134 context: &ExecutionContext,
135 classification: &Arc<QueryClassification>,
136 ) -> Result<Option<Box<dyn QueryResult>>> {
137 AggregationExecutor::new(self).try_aggregation_pushdown(
138 table,
139 statement,
140 context,
141 classification,
142 )
143 }
144
145 fn try_filtered_aggregation_pushdown(
146 &self,
147 table: &dyn radixdb_storage::traits::Table,
148 statement: &SelectStatement,
149 context: &ExecutionContext,
150 classification: &Arc<QueryClassification>,
151 columns: &[String],
152 ) -> Result<Option<Box<dyn QueryResult>>> {
153 AggregationExecutor::new(self).try_filtered_aggregation_pushdown(
154 table,
155 statement,
156 context,
157 classification,
158 columns,
159 )
160 }
161
162 fn try_streaming_global_aggregation(
163 &self,
164 table: &dyn radixdb_storage::traits::Table,
165 statement: &SelectStatement,
166 context: &ExecutionContext,
167 classification: &Arc<QueryClassification>,
168 ) -> Result<Option<Box<dyn QueryResult>>> {
169 AggregationExecutor::new(self).try_streaming_global_aggregation(
170 table,
171 statement,
172 context,
173 classification,
174 )
175 }
176
177 fn try_streaming_derived_table_aggregation(
178 &self,
179 source: Box<dyn QueryResult>,
180 statement: &SelectStatement,
181 classification: &Arc<QueryClassification>,
182 context: &ExecutionContext,
183 ) -> Result<DerivedAggregationAttempt> {
184 AggregationExecutor::new(self).try_streaming_derived_table_aggregation(
185 source,
186 statement,
187 classification,
188 context,
189 )
190 }
191
192 fn try_storage_aggregation(
193 &self,
194 table: &dyn radixdb_storage::traits::Table,
195 statement: &SelectStatement,
196 context: &ExecutionContext,
197 columns: &[String],
198 classification: &QueryClassification,
199 ) -> Option<Box<dyn QueryResult>> {
200 AggregationExecutor::new(self).try_storage_aggregation(
201 table,
202 statement,
203 context,
204 columns,
205 classification,
206 )
207 }
208
209 fn try_fast_count_distinct_compiled(
210 &self,
211 statement: &SelectStatement,
212 compiled: &RwLock<CompiledExecution>,
213 ) -> Option<Result<Box<dyn QueryResult>>> {
214 AggregationExecutor::new(self).try_fast_count_distinct_compiled(statement, compiled)
215 }
216
217 fn try_fast_count_star_compiled(
218 &self,
219 statement: &SelectStatement,
220 compiled: &RwLock<CompiledExecution>,
221 ) -> Option<Result<Box<dyn QueryResult>>> {
222 AggregationExecutor::new(self).try_fast_count_star_compiled(statement, compiled)
223 }
224}
225
226impl<T: AggregationHost + ?Sized> AggregationExecutorExt for T {}
227
228#[derive(Clone, Debug)]
230struct HavingCondition {
231 agg_index: usize,
233 op: ComparisonOp,
235 threshold: f64,
237}
238
239#[derive(Clone, Debug)]
243struct SimpleHavingFilter {
244 conditions: Vec<HavingCondition>,
246}
247
248#[derive(Clone, Copy, Debug)]
249enum ComparisonOp {
250 Gt,
251 Gte,
252 Lt,
253 Lte,
254 Eq,
255 Neq,
256}
257
258impl HavingCondition {
259 fn matches(&self, value: f64) -> bool {
261 match self.op {
262 ComparisonOp::Gt => value > self.threshold,
263 ComparisonOp::Gte => value >= self.threshold,
264 ComparisonOp::Lt => value < self.threshold,
265 ComparisonOp::Lte => value <= self.threshold,
266 ComparisonOp::Eq => (value - self.threshold).abs() < f64::EPSILON,
267 ComparisonOp::Neq => (value - self.threshold).abs() >= f64::EPSILON,
268 }
269 }
270}
271
272impl SimpleHavingFilter {
273 fn single(agg_index: usize, op: ComparisonOp, threshold: f64) -> Self {
275 Self {
276 conditions: vec![HavingCondition {
277 agg_index,
278 op,
279 threshold,
280 }],
281 }
282 }
283
284 fn and(mut self, other: Self) -> Self {
286 self.conditions.extend(other.conditions);
287 self
288 }
289}
290
291#[derive(Clone)]
294enum SimpleAgg {
295 Count(Option<usize>), Sum(usize), Avg(usize), Min(usize), Max(usize), }
301
302impl SimpleAgg {
303 #[inline]
304 fn count_includes_row(&self, row: &Row) -> bool {
305 match self {
306 Self::Count(None) => true,
307 Self::Count(Some(column_index)) => {
308 row.get(*column_index).is_some_and(|value| !value.is_null())
309 }
310 _ => false,
311 }
312 }
313}
314
315pub enum DerivedAggregationAttempt {
321 Applied(Box<dyn QueryResult>),
322 Rejected(Box<dyn QueryResult>),
323}
324
325struct DerivedAggregationPlan {
326 group_col_name: String,
327 group_col_idx: usize,
328 aggregations: Vec<SqlAggregateFunction>,
329 simple_aggs: Vec<SimpleAgg>,
330}
331
332fn try_parse_simple_having(
336 having: &Expression,
337 aggregations: &[SqlAggregateFunction],
338) -> Option<SimpleHavingFilter> {
339 if let Expression::Infix(binop) = having {
341 if binop.operator.eq_ignore_ascii_case("AND") {
342 let left = try_parse_simple_having(&binop.left, aggregations)?;
343 let right = try_parse_simple_having(&binop.right, aggregations)?;
344 return Some(left.and(right));
345 }
346 }
347
348 try_parse_single_having_condition(having, aggregations)
350 .map(|(agg_index, op, threshold)| SimpleHavingFilter::single(agg_index, op, threshold))
351}
352
353fn try_parse_single_having_condition(
355 having: &Expression,
356 aggregations: &[SqlAggregateFunction],
357) -> Option<(usize, ComparisonOp, f64)> {
358 if let Expression::Infix(binop) = having {
360 let (op, threshold) = match binop.operator.as_str() {
361 ">" => (ComparisonOp::Gt, extract_numeric_value(&binop.right)?),
362 ">=" => (ComparisonOp::Gte, extract_numeric_value(&binop.right)?),
363 "<" => (ComparisonOp::Lt, extract_numeric_value(&binop.right)?),
364 "<=" => (ComparisonOp::Lte, extract_numeric_value(&binop.right)?),
365 "=" => (ComparisonOp::Eq, extract_numeric_value(&binop.right)?),
366 "!=" | "<>" => (ComparisonOp::Neq, extract_numeric_value(&binop.right)?),
367 _ => return None,
368 };
369
370 if let Expression::FunctionCall(func) = &*binop.left {
372 let func_upper = func.function.to_uppercase();
373 if matches!(func_upper.as_str(), "SUM" | "COUNT" | "AVG" | "MIN" | "MAX") {
374 for (i, agg) in aggregations.iter().enumerate() {
376 if agg.name.to_uppercase() == func_upper && !agg.distinct {
377 let col_matches = if func_upper == "COUNT" {
379 func.arguments.first().is_none_or(|arg| {
381 matches!(arg, Expression::Star(_))
382 || match arg {
383 Expression::Identifier(id) => {
384 id.value_lower == agg.column_lower
385 }
386 _ => false,
387 }
388 })
389 } else {
390 func.arguments.first().is_some_and(|arg| match arg {
392 Expression::Identifier(id) => id.value_lower == agg.column_lower,
393 _ => false,
394 })
395 };
396
397 if col_matches {
398 return Some((i, op, threshold));
399 }
400 }
401 }
402 }
403 }
404 }
405
406 None
407}
408
409fn extract_numeric_value(expr: &Expression) -> Option<f64> {
411 match expr {
412 Expression::IntegerLiteral(lit) => Some(lit.value as f64),
413 Expression::FloatLiteral(lit) => Some(lit.value),
414 Expression::Prefix(unary) if unary.operator == "-" => {
415 extract_numeric_value(&unary.right).map(|v| -v)
416 }
417 _ => None,
418 }
419}
420
421#[derive(Clone, Debug)]
425struct GroupingSet {
426 active_columns: Vec<bool>,
429}
430
431fn expression_canonical_key(expr: &Expression) -> String {
435 match expr {
436 Expression::Identifier(id) => id.value_lower.to_string(),
437 Expression::QualifiedIdentifier(qid) => {
438 format!("{}.{}", qid.qualifier.value_lower, qid.name.value_lower)
439 }
440 Expression::IntegerLiteral(lit) => format!("$pos:{}", lit.value),
441 Expression::FloatLiteral(lit) => format!("$float:{}", lit.value),
442 Expression::StringLiteral(lit) => format!("$str:{}", lit.value.to_lowercase()),
443 Expression::BooleanLiteral(lit) => format!("$bool:{}", lit.value),
444 Expression::FunctionCall(func) => {
445 let args: Vec<String> = func
447 .arguments
448 .iter()
449 .map(expression_canonical_key)
450 .collect();
451 format!("{}({})", func.function.to_lowercase(), args.join(","))
452 }
453 Expression::Infix(bin) => {
454 format!(
456 "({} {} {})",
457 expression_canonical_key(&bin.left),
458 bin.operator.to_lowercase(),
459 expression_canonical_key(&bin.right)
460 )
461 }
462 Expression::Prefix(un) => {
463 format!(
465 "({}{})",
466 un.operator.to_lowercase(),
467 expression_canonical_key(&un.right)
468 )
469 }
470 Expression::Aliased(aliased) => {
471 expression_canonical_key(&aliased.expression)
473 }
474 _ => format!("{}", expr).to_lowercase(),
476 }
477}
478
479fn group_by_item_canonical_key(item: &GroupByItem) -> String {
481 match item {
482 GroupByItem::Column(name) => name.to_lowercase(),
483 GroupByItem::Position(pos) => format!("$pos:{}", pos),
484 GroupByItem::Expression { expr, .. } => expression_canonical_key(expr),
485 }
486}
487
488#[derive(Clone, Debug)]
490#[allow(clippy::large_enum_variant)]
491pub enum GroupByItem {
492 Column(String),
494 Position(usize),
496 Expression {
498 expr: Expression,
500 display_name: String,
502 },
503}
504
505#[derive(Clone, Debug)]
507enum ColumnSource {
508 AggColumn(String),
510 Expression(Box<Expression>),
512 CorrelatedExpression(Box<Expression>),
514 GroupingFlag(usize),
516}
517
518#[inline]
524fn hash_group_key(values: &[Value]) -> u64 {
525 let mut hasher = AHasher::default();
526 for v in values {
527 v.hash(&mut hasher);
528 }
529 hasher.finish()
530}
531
532#[inline]
533fn track_distinct_value(seen: &mut ValueSet, value: &Value) -> bool {
534 seen.insert(value.clone())
535}
536
537struct GroupEntry {
539 key_values: Vec<Value>,
541 row_indices: Vec<usize>,
543}
544
545#[derive(Clone, Debug)]
547pub struct SqlAggregateFunction {
548 pub name: String,
550 pub column: String,
552 pub column_lower: String,
554 pub alias: Option<String>,
556 pub distinct: bool,
558 pub extra_args: Vec<Value>,
560 pub expression: Option<Expression>,
563 pub order_by: Vec<radixdb_sql::ast::OrderByExpression>,
565 pub filter: Option<Expression>,
567 pub hidden: bool,
569}
570
571impl SqlAggregateFunction {
572 pub fn get_column_name(&self) -> String {
574 if let Some(ref alias) = self.alias {
575 alias.clone()
576 } else if self.column == "*" {
577 format!("{}(*)", self.name)
578 } else if self.extra_args.is_empty() {
579 format!("{}({})", self.name, self.column)
580 } else {
581 let args_str: Vec<String> = std::iter::once(self.column.clone())
583 .chain(self.extra_args.iter().map(|v| match v {
584 Value::Text(s) => format!("'{}'", s),
585 other => other.to_string(),
586 }))
587 .collect();
588 format!("{}({})", self.name, args_str.join(", "))
589 }
590 }
591
592 pub fn get_expression_name(&self) -> String {
595 if self.column == "*" {
596 format!("{}(*)", self.name)
597 } else {
598 format!("{}({})", self.name, self.column)
599 }
600 }
601}