1use super::{fold_columns, fold_vals, DynProofPlan};
2use crate::{
3 base::{
4 database::{
5 group_by_util::{aggregate_columns, AggregatedColumns},
6 Column, ColumnField, ColumnRef, ColumnType, LiteralValue, Table, TableEvaluation,
7 TableRef,
8 },
9 map::{IndexMap, IndexSet},
10 proof::{PlaceholderResult, ProofError},
11 scalar::Scalar,
12 slice_ops,
13 },
14 sql::{
15 proof::{
16 FinalRoundBuilder, FirstRoundBuilder, ProofPlan, ProverEvaluate,
17 SumcheckSubpolynomialType, VerificationBuilder,
18 },
19 proof_exprs::{AliasedDynProofExpr, DynProofExpr, ProofExpr},
20 proof_gadgets::{
21 final_round_evaluate_monotonic, first_round_evaluate_monotonic,
22 fold_log_expr::FoldLogExpr, verify_monotonic,
23 },
24 },
25 utils::log,
26};
27use alloc::{boxed::Box, vec, vec::Vec};
28use bumpalo::Bump;
29use core::iter;
30use num_traits::One;
31use serde::{Deserialize, Serialize};
32use snafu::Snafu;
33use sqlparser::ast::Ident;
34use tracing::{span, Level};
35
36#[non_exhaustive]
38#[derive(Clone, Copy, Debug, Eq, PartialEq, Snafu)]
39pub enum AggregateExecError {
40 #[snafu(display("grouping and deduplication support at most one expression, found {count}"))]
42 UnsupportedGroupByExpressionCount {
43 count: usize,
45 },
46 #[snafu(display("grouping and deduplication do not support expressions of type {data_type}"))]
48 UnsupportedGroupByExpressionType {
49 data_type: ColumnType,
51 },
52}
53
54#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
66pub struct AggregateExec {
67 group_by_exprs: Vec<AliasedDynProofExpr>,
68 sum_expr: Vec<AliasedDynProofExpr>,
69 count_alias: Ident,
70 input: Box<DynProofPlan>,
71 where_clause: DynProofExpr,
72}
73
74impl AggregateExec {
75 pub fn try_new(
77 group_by_exprs: Vec<AliasedDynProofExpr>,
78 sum_expr: Vec<AliasedDynProofExpr>,
79 count_alias: Ident,
80 input: Box<DynProofPlan>,
81 where_clause: DynProofExpr,
82 ) -> Result<Self, AggregateExecError> {
83 let group_by = Self {
84 group_by_exprs,
85 sum_expr,
86 count_alias,
87 input,
88 where_clause,
89 };
90 group_by.try_get_is_uniqueness_provable()?;
91 Ok(group_by)
92 }
93
94 pub fn input(&self) -> &DynProofPlan {
96 &self.input
97 }
98
99 pub fn where_clause(&self) -> &DynProofExpr {
101 &self.where_clause
102 }
103
104 pub fn group_by_exprs(&self) -> &[AliasedDynProofExpr] {
106 &self.group_by_exprs
107 }
108
109 pub fn sum_expr(&self) -> &[AliasedDynProofExpr] {
111 &self.sum_expr
112 }
113
114 pub fn count_alias(&self) -> &Ident {
116 &self.count_alias
117 }
118
119 pub fn try_get_is_uniqueness_provable(&self) -> Result<bool, AggregateExecError> {
122 if let [group_by_expr] = self.group_by_exprs.as_slice() {
123 let data_type = group_by_expr.expr.data_type();
124 if matches!(data_type, ColumnType::VarChar | ColumnType::VarBinary) {
125 Err(AggregateExecError::UnsupportedGroupByExpressionType { data_type })
126 } else {
127 Ok(true)
128 }
129 } else if self.group_by_exprs.is_empty() {
130 Ok(false)
131 } else {
132 Err(AggregateExecError::UnsupportedGroupByExpressionCount {
133 count: self.group_by_exprs.len(),
134 })
135 }
136 }
137}
138
139impl ProofPlan for AggregateExec {
140 fn verifier_evaluate<S: Scalar>(
141 &self,
142 builder: &mut impl VerificationBuilder<S>,
143 accessor: &IndexMap<TableRef, IndexMap<Ident, S>>,
144 chi_eval_map: &IndexMap<TableRef, (S, usize)>,
145 params: &[LiteralValue],
146 ) -> Result<TableEvaluation<S>, ProofError> {
147 let alpha = builder.try_consume_post_result_challenge()?;
148 let beta = builder.try_consume_post_result_challenge()?;
149 let input_eval = self
150 .input
151 .verifier_evaluate(builder, accessor, chi_eval_map, params)?;
152 let input_chi_eval = input_eval.chi_eval();
153 let input_schema = self.input.get_column_result_fields();
155 let accessor = input_schema
157 .iter()
158 .zip(input_eval.column_evals())
159 .map(|(field, eval)| (field.name().clone(), *eval))
160 .collect::<IndexMap<_, _>>();
161
162 let fold_gadget = FoldLogExpr::new(alpha, beta);
164 let group_by_evals = self
165 .group_by_exprs
166 .iter()
167 .map(|aliased_expr| {
168 aliased_expr
169 .expr
170 .verifier_evaluate(builder, &accessor, input_chi_eval, params)
171 })
172 .collect::<Result<Vec<_>, _>>()?;
173 let g_in_star_eval = fold_gadget
174 .verify_evaluate(builder, &group_by_evals, input_chi_eval)?
175 .0;
176 let where_eval =
179 self.where_clause
180 .verifier_evaluate(builder, &accessor, input_chi_eval, params)?;
181
182 let aggregate_evals = self
184 .sum_expr
185 .iter()
186 .map(|aliased_expr| {
187 aliased_expr
188 .expr
189 .verifier_evaluate(builder, &accessor, input_chi_eval, params)
190 })
191 .collect::<Result<Vec<_>, _>>()?;
192 let sum_in_fold_eval = input_chi_eval + beta * fold_vals(beta, &aggregate_evals);
193 let output_chi_eval = builder.try_consume_chi_evaluation()?;
196
197 let group_by_result_columns_evals =
199 builder.try_consume_first_round_mle_evaluations(self.group_by_exprs.len())?;
200 let g_out_star_eval = fold_gadget
201 .verify_evaluate(builder, &group_by_result_columns_evals, output_chi_eval.0)?
202 .0;
203
204 match self.try_get_is_uniqueness_provable() {
205 Ok(true) => {
206 verify_monotonic::<S, true, true>(
207 builder,
208 alpha,
209 beta,
210 group_by_result_columns_evals[0],
211 output_chi_eval.0,
212 )?;
213 }
214 Ok(false) => (),
215 Err(_) => {
216 Err(ProofError::UnsupportedQueryPlan {
217 error: "AggregateExec with nonzero grouping columns and without provable uniqueness check not supported.",
218 })?;
219 }
220 }
221
222 let sum_result_columns_evals =
223 builder.try_consume_first_round_mle_evaluations(self.sum_expr.len() + 1)?;
224
225 let sum_out_fold_eval = fold_vals(beta, &sum_result_columns_evals);
226
227 builder.try_produce_sumcheck_subpolynomial_evaluation(
228 SumcheckSubpolynomialType::ZeroSum,
229 g_in_star_eval * where_eval * sum_in_fold_eval - g_out_star_eval * sum_out_fold_eval,
230 3,
231 )?;
232
233 let column_evals = group_by_result_columns_evals
234 .into_iter()
235 .chain(sum_result_columns_evals)
236 .collect::<Vec<_>>();
237 Ok(TableEvaluation::new(column_evals, output_chi_eval))
238 }
239
240 fn get_column_result_fields(&self) -> Vec<ColumnField> {
241 self.group_by_exprs
242 .iter()
243 .map(|aliased_expr| {
244 ColumnField::new(aliased_expr.alias.clone(), aliased_expr.expr.data_type())
245 })
246 .chain(self.sum_expr.iter().map(|aliased_expr| {
247 ColumnField::new(aliased_expr.alias.clone(), aliased_expr.expr.data_type())
248 }))
249 .chain(iter::once(ColumnField::new(
250 self.count_alias.clone(),
251 ColumnType::BigInt,
252 )))
253 .collect()
254 }
255
256 fn get_column_references(&self) -> IndexSet<ColumnRef> {
257 self.input.get_column_references()
258 }
259
260 fn get_table_references(&self) -> IndexSet<TableRef> {
261 self.input.get_table_references()
262 }
263}
264
265impl ProverEvaluate for AggregateExec {
266 #[tracing::instrument(
267 name = "AggregateExec::first_round_evaluate",
268 level = "debug",
269 skip_all
270 )]
271 fn first_round_evaluate<'a, S: Scalar>(
272 &self,
273 builder: &mut FirstRoundBuilder<'a, S>,
274 alloc: &'a Bump,
275 table_map: &IndexMap<TableRef, Table<'a, S>>,
276 params: &[LiteralValue],
277 ) -> PlaceholderResult<Table<'a, S>> {
278 log::log_memory_usage("Start");
279
280 builder.request_post_result_challenges(2);
281
282 let input = self
283 .input
284 .first_round_evaluate(builder, alloc, table_map, params)?;
285
286 let group_by_columns = self
288 .group_by_exprs
289 .iter()
290 .map(|aliased_expr| -> PlaceholderResult<Column<'a, S>> {
291 aliased_expr
292 .expr
293 .first_round_evaluate(alloc, &input, params)
294 })
295 .collect::<PlaceholderResult<Vec<_>>>()?;
296 let selection_column: Column<'a, S> = self
299 .where_clause
300 .first_round_evaluate(alloc, &input, params)?;
301 let selection = selection_column
302 .as_boolean()
303 .expect("selection is not boolean");
304
305 let sum_columns = self
307 .sum_expr
308 .iter()
309 .map(|aliased_expr| -> PlaceholderResult<Column<'a, S>> {
310 aliased_expr
311 .expr
312 .first_round_evaluate(alloc, &input, params)
313 })
314 .collect::<PlaceholderResult<Vec<_>>>()?;
315 let AggregatedColumns {
319 group_by_columns: group_by_result_columns,
320 sum_columns: sum_result_columns,
321 count_column,
322 ..
323 } = aggregate_columns(alloc, &group_by_columns, &sum_columns, &[], &[], selection)
324 .expect("columns should be aggregatable");
325 for column in &group_by_result_columns {
326 builder.produce_intermediate_mle(*column);
327 }
328
329 builder.produce_chi_evaluation_length(count_column.len());
330
331 let sum_result_columns_iter = sum_result_columns
332 .iter()
333 .map(|col| Column::Scalar(col))
334 .chain(iter::once(Column::BigInt(count_column)));
335 let res = Table::<'a, S>::try_from_iter(
336 self.get_column_result_fields()
337 .into_iter()
338 .map(|field| field.name())
339 .zip(
340 group_by_result_columns
341 .iter()
342 .copied()
343 .chain(sum_result_columns_iter.clone()),
344 ),
345 )
346 .expect("Failed to create table from column references");
347 if self
349 .try_get_is_uniqueness_provable()
350 .expect("Group by must be provable")
351 {
352 first_round_evaluate_monotonic(
353 builder,
354 alloc,
355 alloc.alloc_slice_copy(&group_by_result_columns[0].to_scalar()),
356 );
357 }
358 for column in sum_result_columns_iter {
360 builder.produce_intermediate_mle(column);
361 }
362
363 log::log_memory_usage("End");
364
365 Ok(res)
366 }
367
368 #[expect(clippy::too_many_lines)]
369 #[tracing::instrument(
370 name = "AggregateExec::final_round_evaluate",
371 level = "debug",
372 skip_all
373 )]
374 fn final_round_evaluate<'a, S: Scalar>(
375 &self,
376 builder: &mut FinalRoundBuilder<'a, S>,
377 alloc: &'a Bump,
378 table_map: &IndexMap<TableRef, Table<'a, S>>,
379 params: &[LiteralValue],
380 ) -> PlaceholderResult<Table<'a, S>> {
381 log::log_memory_usage("Start");
382
383 let alpha = builder.consume_post_result_challenge();
384 let beta = builder.consume_post_result_challenge();
385
386 let input = self
387 .input
388 .final_round_evaluate(builder, alloc, table_map, params)?;
389
390 let n = input.num_rows();
391
392 let group_by_columns = self
394 .group_by_exprs
395 .iter()
396 .map(|aliased_expr| -> PlaceholderResult<Column<'a, S>> {
397 aliased_expr
398 .expr
399 .final_round_evaluate(builder, alloc, &input, params)
400 })
401 .collect::<PlaceholderResult<Vec<_>>>()?;
402 let fold_gadget = FoldLogExpr::new(alpha, beta);
403 let g_in_star = fold_gadget
404 .final_round_evaluate(builder, alloc, &group_by_columns, n)
405 .0;
406 let selection_column: Column<'a, S> = self
409 .where_clause
410 .final_round_evaluate(builder, alloc, &input, params)?;
411 let selection = selection_column
412 .as_boolean()
413 .expect("selection is not boolean");
414
415 let span = span!(
417 Level::DEBUG,
418 "AggregateExec::final_round_evaluate sum_columns"
419 )
420 .entered();
421 let sum_columns = self
422 .sum_expr
423 .iter()
424 .map(|aliased_expr| -> PlaceholderResult<Column<'a, S>> {
425 aliased_expr
426 .expr
427 .final_round_evaluate(builder, alloc, &input, params)
428 })
429 .collect::<PlaceholderResult<Vec<_>>>()?;
430 span.exit();
431
432 let span = span!(
433 Level::DEBUG,
434 "AggregateExec::final_round_evaluate allocate sum_in_fold"
435 )
436 .entered();
437 let sum_in_fold = alloc.alloc_slice_fill_copy(n, One::one());
438 span.exit();
439
440 fold_columns(sum_in_fold, beta, beta, &sum_columns);
441 let AggregatedColumns {
445 group_by_columns: group_by_result_columns,
446 sum_columns: sum_result_columns,
447 count_column,
448 ..
449 } = aggregate_columns(alloc, &group_by_columns, &sum_columns, &[], &[], selection)
450 .expect("columns should be aggregatable");
451
452 let m = count_column.len();
453
454 let g_out_star = fold_gadget
455 .final_round_evaluate(builder, alloc, &group_by_result_columns, m)
456 .0;
457
458 if self
459 .try_get_is_uniqueness_provable()
460 .expect("Group by must be provable")
461 {
462 let g_out_scalars = group_by_result_columns[0].to_scalar();
463 let alloc_g_out_scalars = alloc.alloc_slice_copy(&g_out_scalars);
464 final_round_evaluate_monotonic::<S, true, true>(
465 builder,
466 alloc,
467 alpha,
468 beta,
469 alloc_g_out_scalars,
470 );
471 }
472
473 let sum_result_columns_iter = sum_result_columns.iter().map(|col| Column::Scalar(col));
475 let columns = group_by_result_columns
476 .clone()
477 .into_iter()
478 .chain(sum_result_columns_iter.clone())
479 .chain(iter::once(Column::BigInt(count_column)));
480 let res = Table::<'a, S>::try_from_iter(
481 self.get_column_result_fields()
482 .into_iter()
483 .map(|field| field.name())
484 .zip(columns.clone()),
485 )
486 .expect("Failed to create table from column references");
487 let sum_out_fold = alloc.alloc_slice_fill_default(m);
490 slice_ops::slice_cast_mut(count_column, sum_out_fold);
491 fold_columns(sum_out_fold, beta, beta, &sum_result_columns);
492
493 builder.produce_sumcheck_subpolynomial(
494 SumcheckSubpolynomialType::ZeroSum,
495 vec![
496 (
497 S::one(),
498 vec![
499 Box::new(g_in_star as &[_]),
500 Box::new(selection),
501 Box::new(sum_in_fold as &[_]),
502 ],
503 ),
504 (
505 -S::one(),
506 vec![Box::new(g_out_star as &[_]), Box::new(sum_out_fold as &[_])],
507 ),
508 ],
509 );
510
511 log::log_memory_usage("End");
512
513 Ok(res)
514 }
515}