reifydb_sub_flow/operator/window/
aggregation.rs1use std::sync::LazyLock;
5
6use postcard::to_stdvec;
7use reifydb_codec::{
8 encoded::shape::{RowShape, RowShapeField},
9 key::{encoded::EncodedKey, serializer::KeySerializer},
10};
11use reifydb_core::{
12 interface::catalog::flow::FlowNodeId,
13 row::Row,
14 value::column::{ColumnWithName, columns::Columns},
15};
16use reifydb_engine::{
17 expression::{
18 compile::{CompiledExpr, compile_expression},
19 context::{CompileContext, EvalContext},
20 },
21 flow::aggregate::{AggregateContext, SlotArg, SlotKind, rewrite_aggregates, synthetic_aggregate_column_name},
22 vm::stack::SymbolTable,
23};
24use reifydb_routine::routine::registry::Routines;
25use reifydb_rql::expression::{Expression, name::display_label};
26use reifydb_runtime::context::RuntimeContext;
27use reifydb_value::{
28 Result,
29 error::Error,
30 params::Params,
31 util::hash::{Hash128, xxh3_128},
32 value::{Value, identity::IdentityId, row_number::RowNumber, value_type::ValueType},
33};
34
35use crate::{error::FlowStateError, operator::OperatorCell};
36
37static EMPTY_PARAMS: Params = Params::None;
38
39static EMPTY_SYMBOL_TABLE: LazyLock<SymbolTable> = LazyLock::new(SymbolTable::new);
40
41#[derive(Clone, Debug)]
42pub enum SlotInput {
43 Star,
44 Column(String),
45 Expr(usize),
46}
47
48#[inline]
49fn build_aggregation_shape(names: &[String], types: &[ValueType]) -> RowShape {
50 let fields: Vec<RowShapeField> = names
51 .iter()
52 .zip(types.iter())
53 .map(|(name, ty)| RowShapeField::unconstrained(name.clone(), ty.clone()))
54 .collect();
55 RowShape::new(fields)
56}
57
58pub struct Aggregation {
59 pub node: FlowNodeId,
60 pub parent: OperatorCell,
61 pub compiled_group_by: Vec<CompiledExpr>,
62 pub group_names: Vec<String>,
63 pub aggregate_output_names: Vec<String>,
64
65 pub slot_kinds: Option<Vec<SlotKind>>,
66
67 pub slot_inputs: Vec<SlotInput>,
68
69 pub compiled_slot_args: Vec<CompiledExpr>,
70
71 pub compiled_outputs: Vec<CompiledExpr>,
72
73 pub routines: Routines,
74 pub runtime_context: RuntimeContext,
75}
76
77impl Aggregation {
78 pub fn new(
79 node: FlowNodeId,
80 parent: OperatorCell,
81 group_by: Vec<Expression>,
82 aggregations: Vec<Expression>,
83 routines: Routines,
84 runtime_context: RuntimeContext,
85 context: AggregateContext,
86 ) -> Self {
87 let symbols = SymbolTable::new();
88 let compile_ctx = CompileContext {
89 symbols: &symbols,
90 };
91
92 let compiled_group_by: Vec<CompiledExpr> = group_by
93 .iter()
94 .map(|e| compile_expression(&compile_ctx, e).expect("Failed to compile group_by expression"))
95 .collect();
96
97 let aggregate_output_names: Vec<String> =
98 aggregations.iter().map(|e| display_label(e).text().to_string()).collect();
99
100 let mut slots: Vec<(SlotKind, SlotArg)> = Vec::new();
101 let mut rewritten_outputs: Vec<Expression> = Vec::new();
102 let mut all_representable = !aggregations.is_empty();
103 for aggregate in &aggregations {
104 let mut expr = aggregate.clone();
105 if rewrite_aggregates(&routines, &mut expr, &mut slots, context) {
106 rewritten_outputs.push(expr);
107 } else {
108 all_representable = false;
109 break;
110 }
111 }
112 let (slot_kinds, slot_inputs, compiled_slot_args, compiled_outputs) = if all_representable {
113 let mut kinds = Vec::with_capacity(slots.len());
114 let mut inputs = Vec::with_capacity(slots.len());
115 let mut compiled_args = Vec::new();
116 for (kind, arg) in slots {
117 kinds.push(kind);
118 inputs.push(match arg {
119 SlotArg::Star => SlotInput::Star,
120 SlotArg::Column(name) => SlotInput::Column(name),
121 SlotArg::Expr(expr) => {
122 let idx = compiled_args.len();
123 compiled_args.push(compile_expression(&compile_ctx, &expr)
124 .expect("Failed to compile aggregation argument expression"));
125 SlotInput::Expr(idx)
126 }
127 });
128 }
129 let outputs: Vec<CompiledExpr> = rewritten_outputs
130 .iter()
131 .map(|e| {
132 compile_expression(&compile_ctx, e)
133 .expect("Failed to compile rewritten output expression")
134 })
135 .collect();
136 (Some(kinds), inputs, compiled_args, outputs)
137 } else {
138 (None, Vec::new(), Vec::new(), Vec::new())
139 };
140 let group_names: Vec<String> = group_by.iter().map(|e| display_label(e).text().to_string()).collect();
141
142 Self {
143 node,
144 parent,
145 compiled_group_by,
146 group_names,
147 aggregate_output_names,
148 slot_kinds,
149 slot_inputs,
150 compiled_slot_args,
151 compiled_outputs,
152 routines,
153 runtime_context,
154 }
155 }
156
157 pub fn create_window_key(&self, group_hash: Hash128, window_id: u64) -> EncodedKey {
158 let mut serializer = KeySerializer::with_capacity(32);
159 serializer.extend_bytes(b"win:");
160 serializer.extend_u128(group_hash);
161 serializer.extend_u64(window_id);
162 serializer.finish()
163 }
164
165 pub(super) fn create_engine_meta_key(&self, group_hash: Hash128, window_start: u64) -> EncodedKey {
166 let mut serializer = KeySerializer::with_capacity(32);
167 serializer.extend_bytes(b"ewm:");
168 serializer.extend_u128(group_hash);
169 serializer.extend_u64(window_start);
170 serializer.finish()
171 }
172
173 pub fn compute_groups(&self, columns: &Columns) -> Result<Vec<(Hash128, Vec<Value>)>> {
174 let row_count = columns.row_count();
175 if row_count == 0 {
176 return Ok(Vec::new());
177 }
178 if self.compiled_group_by.is_empty() {
179 return Ok(vec![(Hash128::from(0u128), Vec::new()); row_count]);
180 }
181
182 let session = self.eval_session();
183 let exec_ctx = session.with_eval(columns.clone(), row_count);
184 let mut group_columns: Vec<ColumnWithName> = Vec::new();
185 for compiled_expr in &self.compiled_group_by {
186 group_columns.push(compiled_expr.execute(&exec_ctx)?);
187 }
188
189 let mut out = Vec::with_capacity(row_count);
190 let mut buf = Vec::with_capacity(128);
191 for row_idx in 0..row_count {
192 buf.clear();
193 let mut values = Vec::with_capacity(group_columns.len());
194 for col in &group_columns {
195 let value = col.data().get_value(row_idx);
196 let bytes = to_stdvec(&value).map_err(|e| {
197 Error::from(FlowStateError::Encode {
198 state: "group-by value",
199 cause: e.to_string(),
200 })
201 })?;
202 buf.extend_from_slice(&bytes);
203 values.push(value);
204 }
205 out.push((xxh3_128(&buf), values));
206 }
207 Ok(out)
208 }
209
210 pub fn evaluate_slot_inputs(&self, columns: &Columns) -> Result<Vec<ColumnWithName>> {
211 if self.compiled_slot_args.is_empty() {
212 return Ok(Vec::new());
213 }
214 let row_count = columns.row_count();
215 let session = self.eval_session();
216 let exec_ctx = session.with_eval(columns.clone(), row_count);
217 let mut out = Vec::with_capacity(self.compiled_slot_args.len());
218 for compiled in &self.compiled_slot_args {
219 out.push(compiled.execute(&exec_ctx)?);
220 }
221 Ok(out)
222 }
223
224 pub fn build_contribution(
225 &self,
226 columns: &Columns,
227 slot_cols: &[ColumnWithName],
228 row_idx: usize,
229 ) -> Vec<Option<Value>> {
230 self.slot_inputs
231 .iter()
232 .map(|input| match input {
233 SlotInput::Star => None,
234 SlotInput::Column(name) => columns.column(name).map(|c| c.data().get_value(row_idx)),
235 SlotInput::Expr(idx) => Some(slot_cols[*idx].data().get_value(row_idx)),
236 })
237 .collect()
238 }
239
240 pub fn compute_outputs(&self, slot_values: &[Value]) -> Result<Vec<Value>> {
241 if self.compiled_outputs.is_empty() {
242 return Ok(slot_values.to_vec());
243 }
244 let names: Vec<String> = (0..slot_values.len()).map(synthetic_aggregate_column_name).collect();
245 let types: Vec<_> = slot_values.iter().map(Value::get_type).collect();
246 let layout = build_aggregation_shape(&names, &types);
247 let mut encoded = layout.allocate();
248 layout.set_values(&mut encoded, slot_values);
249 let row = Row {
250 number: RowNumber(0),
251 encoded,
252 shape: layout,
253 };
254 let columns = Columns::from_row(&row);
255 let session = self.eval_session();
256 let exec_ctx = session.with_eval(columns, 1);
257 let mut out = Vec::with_capacity(self.compiled_outputs.len());
258 for compiled in &self.compiled_outputs {
259 out.push(compiled.execute(&exec_ctx)?.data().get_value(0));
260 }
261 Ok(out)
262 }
263
264 pub fn build_engine_row(
265 &self,
266 group_values: &[Value],
267 slot_values: &[Value],
268 row_number: RowNumber,
269 ts_nanos: u64,
270 ) -> Result<Row> {
271 let aggregate_values = self.compute_outputs(slot_values)?;
272 let mut values = Vec::with_capacity(group_values.len() + aggregate_values.len());
273 let mut names = Vec::with_capacity(group_values.len() + aggregate_values.len());
274 let mut types = Vec::with_capacity(group_values.len() + aggregate_values.len());
275 for (value, name) in group_values.iter().zip(self.group_names.iter()) {
276 types.push(value.get_type());
277 values.push(value.clone());
278 names.push(name.clone());
279 }
280 for (value, name) in aggregate_values.iter().zip(self.aggregate_output_names.iter()) {
281 types.push(value.get_type());
282 values.push(value.clone());
283 names.push(name.clone());
284 }
285 let layout = build_aggregation_shape(&names, &types);
286 let mut encoded = layout.allocate();
287 layout.set_values(&mut encoded, &values);
288 encoded.set_timestamps(ts_nanos, ts_nanos);
289 Ok(Row {
290 number: row_number,
291 encoded,
292 shape: layout,
293 })
294 }
295
296 pub fn current_timestamp(&self) -> u64 {
297 self.runtime_context.clock.now_millis()
298 }
299
300 pub(super) fn eval_session(&self) -> EvalContext<'_> {
301 EvalContext {
302 params: &EMPTY_PARAMS,
303 symbols: &EMPTY_SYMBOL_TABLE,
304 routines: &self.routines,
305 runtime_context: &self.runtime_context,
306 arena: None,
307 identity: IdentityId::root(),
308 is_aggregate_context: false,
309 columns: Columns::empty(),
310 row_count: 1,
311 target: None,
312 take: None,
313 }
314 }
315}