1use std::cmp::Ordering as CmpOrdering;
2use std::collections::{BTreeMap, HashMap, hash_map::Entry};
3use std::env;
4use std::fmt;
5use std::mem;
6use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
7use std::sync::{Arc, OnceLock};
8use std::time::Instant;
9
10use arrow::array::{
11 Array, ArrayRef, Int64Array, RecordBatch, TimestampMicrosecondArray, TimestampMillisecondArray,
12 TimestampNanosecondArray, TimestampSecondArray,
13 types::{IntervalDayTimeType, IntervalMonthDayNanoType},
14};
15use arrow::datatypes::{DataType, Schema, SchemaRef, TimeUnit};
16use datafusion::common::{DataFusionError, Result, ScalarValue};
17use datafusion::logical_expr::Accumulator;
18use datafusion::physical_expr::expressions::Literal;
19use datafusion::physical_expr::{PhysicalExpr, ScalarFunctionExpr};
20use datafusion::physical_plan::ExecutionPlan;
21use datafusion::physical_plan::aggregates::{AggregateExec, AggregateMode};
22use datum::{Source, StreamResult};
23
24use crate::{ChangeOp, ChangelogBatch, SqlEvent, Watermark, stream_error};
25
26#[derive(Clone, Default)]
28pub struct WindowedAggregationMetrics {
29 late_dropped_rows: Arc<AtomicU64>,
30}
31
32impl WindowedAggregationMetrics {
33 pub(crate) fn new(late_dropped_rows: Arc<AtomicU64>) -> Self {
34 Self { late_dropped_rows }
35 }
36
37 #[must_use]
38 pub fn late_dropped_rows(&self) -> u64 {
39 self.late_dropped_rows.load(Ordering::Relaxed)
40 }
41
42 fn record_late_row(&self) {
43 self.late_dropped_rows.fetch_add(1, Ordering::Relaxed);
44 }
45}
46
47impl fmt::Debug for WindowedAggregationMetrics {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 f.debug_struct("WindowedAggregationMetrics")
50 .field("late_dropped_rows", &self.late_dropped_rows())
51 .finish()
52 }
53}
54
55pub(crate) fn windowed_aggregate_source(
56 source: Source<SqlEvent<RecordBatch>>,
57 aggregate: &AggregateExec,
58 metrics: WindowedAggregationMetrics,
59) -> Result<Source<SqlEvent<RecordBatch>>> {
60 let stage = WindowedAggregationStage::try_new(aggregate, metrics, false)?;
61 Ok(source.try_stateful_map_concat(stage, |stage, event| stage.apply_record_event(event)))
62}
63
64pub(crate) fn windowed_changelog_aggregate_source(
65 source: Source<SqlEvent<ChangelogBatch>>,
66 aggregate: &AggregateExec,
67 metrics: WindowedAggregationMetrics,
68) -> Result<Source<SqlEvent<RecordBatch>>> {
69 let stage = WindowedAggregationStage::try_new(aggregate, metrics, true)?;
70 Ok(source.try_stateful_map_concat(stage, |stage, event| stage.apply_changelog_event(event)))
71}
72
73struct WindowedAggregationStage {
74 plan: Arc<WindowAggregatePlan>,
75 metrics: WindowedAggregationMetrics,
76 profile: WindowProfile,
77 retractable_input: bool,
78 current_watermark_ns: Option<i64>,
79 windows: HashMap<WindowGroupKey, WindowEntry>,
80 windows_by_end: BTreeMap<i64, Vec<WindowGroupKey>>,
81}
82
83impl Clone for WindowedAggregationStage {
84 fn clone(&self) -> Self {
85 Self {
86 plan: Arc::clone(&self.plan),
87 metrics: self.metrics.clone(),
88 profile: self.profile.clone(),
89 retractable_input: self.retractable_input,
90 current_watermark_ns: None,
91 windows: HashMap::new(),
92 windows_by_end: BTreeMap::new(),
93 }
94 }
95}
96
97impl Drop for WindowedAggregationStage {
98 fn drop(&mut self) {
99 self.profile.report();
100 }
101}
102
103impl WindowedAggregationStage {
104 fn try_new(
105 aggregate: &AggregateExec,
106 metrics: WindowedAggregationMetrics,
107 retractable_input: bool,
108 ) -> Result<Self> {
109 Ok(Self {
110 plan: Arc::new(WindowAggregatePlan::try_new(aggregate, retractable_input)?),
111 metrics,
112 profile: WindowProfile::new(),
113 retractable_input,
114 current_watermark_ns: None,
115 windows: HashMap::new(),
116 windows_by_end: BTreeMap::new(),
117 })
118 }
119
120 fn apply_record_event(
121 &mut self,
122 event: SqlEvent<RecordBatch>,
123 ) -> StreamResult<Vec<SqlEvent<RecordBatch>>> {
124 match event {
125 SqlEvent::Data(batch) => self.apply_batch(&batch, None).map_err(stream_error),
126 SqlEvent::Watermark(watermark) => self.apply_watermark(watermark).map_err(stream_error),
127 SqlEvent::Barrier(barrier) => Ok(vec![SqlEvent::Barrier(barrier)]),
128 }
129 }
130
131 fn apply_changelog_event(
132 &mut self,
133 event: SqlEvent<ChangelogBatch>,
134 ) -> StreamResult<Vec<SqlEvent<RecordBatch>>> {
135 match event {
136 SqlEvent::Data(changes) => self
137 .apply_batch(changes.batch(), Some(changes.ops()))
138 .map_err(stream_error),
139 SqlEvent::Watermark(watermark) => self.apply_watermark(watermark).map_err(stream_error),
140 SqlEvent::Barrier(barrier) => Ok(vec![SqlEvent::Barrier(barrier)]),
141 }
142 }
143
144 fn apply_batch(
145 &mut self,
146 batch: &RecordBatch,
147 ops: Option<&[ChangeOp]>,
148 ) -> Result<Vec<SqlEvent<RecordBatch>>> {
149 if batch.num_rows() == 0 {
150 return Ok(Vec::new());
151 }
152 if let Some(ops) = ops
153 && ops.len() != batch.num_rows()
154 {
155 return Err(DataFusionError::Plan(format!(
156 "windowed aggregate received {} changelog ops for {} rows",
157 ops.len(),
158 batch.num_rows()
159 )));
160 }
161
162 self.profile.record_batch(batch.num_rows());
163 let prepare_start = self.profile.start_timer();
164 let prepared = self.plan.prepare_batch(batch)?;
165 self.profile.record_prepare(prepare_start);
166 if self.plan.use_batch_grouping() {
167 return self.apply_grouped_batch(batch.num_rows(), &prepared, ops);
168 }
169 let plan = Arc::clone(&self.plan);
170 let profile = self.profile.clone();
171 let mut assignments = Vec::new();
172 for row in 0..batch.num_rows() {
173 let event_time_ns = timestamp_ns_from_array(&prepared.event_times, row)?;
174 if self.row_is_late(event_time_ns) {
175 self.metrics.record_late_row();
176 self.profile.record_late_row();
177 continue;
178 }
179
180 let assignment_start = self.profile.start_timer();
181 self.plan
182 .window
183 .assignments_into(event_time_ns, &mut assignments)?;
184 self.profile
185 .record_assignments(assignment_start, assignments.len());
186 for assignment in assignments.drain(..) {
187 let key_start = self.profile.start_timer();
188 let key = self.plan.group_key(&prepared, row, assignment.start_ns)?;
189 let change = ops.map(|ops| ops[row]).unwrap_or(ChangeOp::Insert);
190 let change = [(row, change)];
191 let entry = self.entry_for_key(key, assignment.end_ns, change[0].1)?;
192 profile.record_key_lookup(key_start);
193 let update_start = profile.start_timer();
194 plan.apply_rows(entry, &prepared, &change)?;
195 profile.record_accumulator_update(update_start);
196 }
197 }
198 Ok(Vec::new())
199 }
200
201 fn apply_grouped_batch(
202 &mut self,
203 rows: usize,
204 prepared: &PreparedBatch,
205 ops: Option<&[ChangeOp]>,
206 ) -> Result<Vec<SqlEvent<RecordBatch>>> {
207 let mut pending = HashMap::<WindowGroupKey, PendingWindowGroup>::new();
208 let mut assignments = Vec::new();
209 for row in 0..rows {
210 let event_time_ns = timestamp_ns_from_array(&prepared.event_times, row)?;
211 if self.row_is_late(event_time_ns) {
212 self.metrics.record_late_row();
213 self.profile.record_late_row();
214 continue;
215 }
216
217 let assignment_start = self.profile.start_timer();
218 self.plan
219 .window
220 .assignments_into(event_time_ns, &mut assignments)?;
221 self.profile
222 .record_assignments(assignment_start, assignments.len());
223 for assignment in assignments.drain(..) {
224 let key = self.plan.group_key(prepared, row, assignment.start_ns)?;
225 let change = ops.map(|ops| ops[row]).unwrap_or(ChangeOp::Insert);
226 pending
227 .entry(key)
228 .or_insert_with(|| PendingWindowGroup::new(assignment.end_ns))
229 .changes
230 .push((row, change));
231 }
232 }
233
234 let plan = Arc::clone(&self.plan);
235 let profile = self.profile.clone();
236 for (key, group) in pending {
237 let key_start = profile.start_timer();
238 let first_change = group
239 .changes
240 .first()
241 .map(|(_row, change)| *change)
242 .unwrap_or(ChangeOp::Insert);
243 let entry = self.entry_for_key(key, group.window_end_ns, first_change)?;
244 profile.record_key_lookup(key_start);
245 let update_start = profile.start_timer();
246 plan.apply_rows(entry, prepared, &group.changes)?;
247 profile.record_accumulator_update(update_start);
248 }
249 Ok(Vec::new())
250 }
251
252 fn entry_for_key(
253 &mut self,
254 key: WindowGroupKey,
255 window_end_ns: i64,
256 first_change: ChangeOp,
257 ) -> Result<&mut WindowEntry> {
258 let open_entries = self.windows.len() + 1;
259 let plan = Arc::clone(&self.plan);
260 let profile = self.profile.clone();
261 match self.windows.entry(key) {
262 Entry::Occupied(entry) => Ok(entry.into_mut()),
263 Entry::Vacant(entry) => {
264 if first_change.is_retraction() {
265 return Err(DataFusionError::Plan(
266 "windowed aggregate received a retraction for an absent open window/key"
267 .into(),
268 ));
269 }
270 let key = entry.key().clone();
271 let window_entry = WindowEntry::new(window_end_ns, plan.create_aggregate_states()?);
272 profile.record_entry_insert(open_entries, &key, &window_entry);
273 self.windows_by_end
274 .entry(window_end_ns)
275 .or_default()
276 .push(key);
277 Ok(entry.insert(window_entry))
278 }
279 }
280 }
281
282 fn apply_watermark(&mut self, watermark: Watermark) -> Result<Vec<SqlEvent<RecordBatch>>> {
283 let watermark_ns = self
284 .current_watermark_ns
285 .map_or(watermark.timestamp_ns(), |current| {
286 current.max(watermark.timestamp_ns())
287 });
288 self.current_watermark_ns = Some(watermark_ns);
289 let mut out = self.emit_ready_windows(watermark_ns)?;
290 out.push(SqlEvent::Watermark(Watermark::new(watermark_ns)));
291 Ok(out)
292 }
293
294 fn row_is_late(&self, event_time_ns: i64) -> bool {
295 self.current_watermark_ns
296 .is_some_and(|watermark_ns| event_time_ns <= watermark_ns)
297 }
298
299 fn emit_ready_windows(&mut self, watermark_ns: i64) -> Result<Vec<SqlEvent<RecordBatch>>> {
300 let ready_ends = self
301 .windows_by_end
302 .keys()
303 .take_while(|end_ns| **end_ns <= watermark_ns)
304 .copied()
305 .collect::<Vec<_>>();
306 let ready_len = ready_ends
307 .iter()
308 .filter_map(|end_ns| self.windows_by_end.get(end_ns).map(Vec::len))
309 .sum();
310 let mut rows = Vec::with_capacity(ready_len);
311 for end_ns in ready_ends {
312 if let Some(mut keys) = self.windows_by_end.remove(&end_ns) {
313 keys.sort_by(WindowGroupKey::sort_cmp);
314 for key in keys {
315 if let Some(mut entry) = self.windows.remove(&key) {
316 rows.push(self.plan.evaluate_entry(&key, &mut entry)?);
317 }
318 }
319 }
320 }
321 if rows.is_empty() {
322 return Ok(Vec::new());
323 }
324 Ok(vec![SqlEvent::Data(self.plan.build_output_batch(rows)?)])
325 }
326}
327
328#[derive(Clone)]
329struct WindowProfile {
330 enabled: bool,
331 counters: Arc<WindowProfileCounters>,
332}
333
334impl WindowProfile {
335 fn new() -> Self {
336 Self {
337 enabled: window_profile_enabled(),
338 counters: Arc::new(WindowProfileCounters::default()),
339 }
340 }
341
342 fn record_batch(&self, rows: usize) {
343 if self.enabled {
344 self.counters.batches.fetch_add(1, Ordering::Relaxed);
345 self.counters
346 .input_rows
347 .fetch_add(rows as u64, Ordering::Relaxed);
348 }
349 }
350
351 fn record_late_row(&self) {
352 if self.enabled {
353 self.counters.late_rows.fetch_add(1, Ordering::Relaxed);
354 }
355 }
356
357 fn start_timer(&self) -> Option<Instant> {
358 self.enabled.then(Instant::now)
359 }
360
361 fn record_prepare(&self, start: Option<Instant>) {
362 self.record_elapsed(&self.counters.prepare_ns, start);
363 }
364
365 fn record_assignments(&self, start: Option<Instant>, assignments: usize) {
366 if self.enabled {
367 self.counters
368 .assignments
369 .fetch_add(assignments as u64, Ordering::Relaxed);
370 self.record_elapsed(&self.counters.assignment_ns, start);
371 }
372 }
373
374 fn record_key_lookup(&self, start: Option<Instant>) {
375 self.record_elapsed(&self.counters.key_lookup_ns, start);
376 }
377
378 fn record_accumulator_update(&self, start: Option<Instant>) {
379 if self.enabled {
380 self.counters.update_calls.fetch_add(1, Ordering::Relaxed);
381 self.record_elapsed(&self.counters.update_ns, start);
382 }
383 }
384
385 fn record_entry_insert(&self, open_entries: usize, key: &WindowGroupKey, entry: &WindowEntry) {
386 if self.enabled {
387 self.counters.entry_inserts.fetch_add(1, Ordering::Relaxed);
388 self.counters
389 .entry_estimated_bytes
390 .fetch_add(estimated_entry_bytes(key, entry) as u64, Ordering::Relaxed);
391 update_max(&self.counters.max_open_entries, open_entries as u64);
392 }
393 }
394
395 fn record_elapsed(&self, counter: &AtomicU64, start: Option<Instant>) {
396 if let Some(start) = start {
397 counter.fetch_add(start.elapsed().as_nanos() as u64, Ordering::Relaxed);
398 }
399 }
400
401 fn report(&self) {
402 if !self.enabled || self.counters.input_rows.load(Ordering::Relaxed) == 0 {
403 return;
404 }
405 if self.counters.reported.swap(true, Ordering::Relaxed) {
406 return;
407 }
408 let batches = self.counters.batches.load(Ordering::Relaxed);
409 let input_rows = self.counters.input_rows.load(Ordering::Relaxed);
410 let assignments = self.counters.assignments.load(Ordering::Relaxed);
411 let update_calls = self.counters.update_calls.load(Ordering::Relaxed);
412 let entry_inserts = self.counters.entry_inserts.load(Ordering::Relaxed);
413 let estimated_bytes = self.counters.entry_estimated_bytes.load(Ordering::Relaxed);
414 let avg_entry_bytes = estimated_bytes.checked_div(entry_inserts).unwrap_or(0);
415 eprintln!(
416 "DATUM_SQL_WINDOW_PROFILE batches={batches} input_rows={input_rows} late_rows={} assignments={assignments} fanout_per_row={:.3} update_calls={update_calls} entry_inserts={entry_inserts} max_open_entries={} avg_entry_estimated_bytes={avg_entry_bytes} prepare_ms={:.3} assignment_ms={:.3} key_lookup_ms={:.3} accumulator_update_ms={:.3}",
417 self.counters.late_rows.load(Ordering::Relaxed),
418 assignments as f64 / input_rows.max(1) as f64,
419 self.counters.max_open_entries.load(Ordering::Relaxed),
420 ns_to_ms(self.counters.prepare_ns.load(Ordering::Relaxed)),
421 ns_to_ms(self.counters.assignment_ns.load(Ordering::Relaxed)),
422 ns_to_ms(self.counters.key_lookup_ns.load(Ordering::Relaxed)),
423 ns_to_ms(self.counters.update_ns.load(Ordering::Relaxed)),
424 );
425 }
426}
427
428#[derive(Default)]
429struct WindowProfileCounters {
430 reported: AtomicBool,
431 batches: AtomicU64,
432 input_rows: AtomicU64,
433 late_rows: AtomicU64,
434 assignments: AtomicU64,
435 update_calls: AtomicU64,
436 entry_inserts: AtomicU64,
437 max_open_entries: AtomicU64,
438 entry_estimated_bytes: AtomicU64,
439 prepare_ns: AtomicU64,
440 assignment_ns: AtomicU64,
441 key_lookup_ns: AtomicU64,
442 update_ns: AtomicU64,
443}
444
445fn window_profile_enabled() -> bool {
446 static ENABLED: OnceLock<bool> = OnceLock::new();
447 *ENABLED.get_or_init(|| {
448 env::var("DATUM_SQL_WINDOW_PROFILE")
449 .is_ok_and(|value| value == "1" || value.eq_ignore_ascii_case("true"))
450 })
451}
452
453fn update_max(counter: &AtomicU64, value: u64) {
454 let mut current = counter.load(Ordering::Relaxed);
455 while value > current {
456 match counter.compare_exchange_weak(current, value, Ordering::Relaxed, Ordering::Relaxed) {
457 Ok(_) => break,
458 Err(next) => current = next,
459 }
460 }
461}
462
463fn estimated_entry_bytes(key: &WindowGroupKey, entry: &WindowEntry) -> usize {
464 let key_heap = match key {
465 WindowGroupKey::FastI64 { values, .. } => match values {
466 FastI64GroupValues::None | FastI64GroupValues::One(_) => 0,
467 FastI64GroupValues::Many(values) => values.capacity() * mem::size_of::<i64>(),
468 },
469 WindowGroupKey::Scalar(values) => values
470 .capacity()
471 .saturating_mul(mem::size_of::<ScalarValue>())
472 .saturating_add(
473 values
474 .iter()
475 .map(|value| value.size().saturating_sub(mem::size_of::<ScalarValue>()))
476 .sum::<usize>(),
477 ),
478 };
479 let state_heap = entry
480 .states
481 .capacity()
482 .saturating_mul(mem::size_of::<AggregateState>())
483 .saturating_add(
484 entry
485 .states
486 .iter()
487 .map(AggregateState::estimated_size)
488 .sum::<usize>(),
489 );
490 mem::size_of::<WindowGroupKey>()
491 .saturating_add(key_heap)
492 .saturating_add(mem::size_of::<WindowEntry>())
493 .saturating_add(state_heap)
494}
495
496fn ns_to_ms(ns: u64) -> f64 {
497 ns as f64 / 1_000_000.0
498}
499
500struct WindowAggregatePlan {
501 output_schema: SchemaRef,
502 window: WindowSpec,
503 group_exprs: Vec<Arc<dyn PhysicalExpr>>,
504 group_types: Vec<DataType>,
505 aggregate_exprs: Vec<Arc<datafusion::physical_expr::aggregate::AggregateFunctionExpr>>,
506 aggregate_kinds: Vec<AggregateKind>,
507 key_kind: WindowGroupKeyKind,
508 use_batch_grouping: bool,
509 group_count: usize,
510 aggregate_count: usize,
511 retractable_input: bool,
512}
513
514impl WindowAggregatePlan {
515 fn try_new(aggregate: &AggregateExec, retractable_input: bool) -> Result<Self> {
516 match aggregate.mode() {
517 AggregateMode::Single | AggregateMode::SinglePartitioned => {}
518 other => {
519 return Err(DataFusionError::NotImplemented(format!(
520 "datum-sql windowed aggregation lowers single-phase aggregates only, found {other:?}"
521 )));
522 }
523 }
524 if !aggregate.group_expr().is_single() || aggregate.group_expr().groups().len() != 1 {
525 return Err(DataFusionError::NotImplemented(
526 "datum-sql windowed aggregation does not support grouping sets, cube, or rollup"
527 .into(),
528 ));
529 }
530 if aggregate.filter_expr().iter().any(Option::is_some) {
531 return Err(DataFusionError::NotImplemented(
532 "datum-sql windowed aggregation does not support aggregate FILTER clauses yet"
533 .into(),
534 ));
535 }
536
537 let group_exprs = aggregate
538 .group_expr()
539 .expr()
540 .iter()
541 .map(|(expr, _name)| Arc::clone(expr))
542 .collect::<Vec<_>>();
543 let input_schema = aggregate.input_schema();
544 let group_types = group_exprs
545 .iter()
546 .map(|expr| expr.data_type(input_schema.as_ref()))
547 .collect::<Result<Vec<_>>>()?;
548 let (window_index, window) = find_window_spec(&group_exprs, input_schema.as_ref())?;
549 if window_index == usize::MAX {
550 return Err(DataFusionError::Internal(
551 "window index sentinel escaped validation".into(),
552 ));
553 }
554 let key_kind =
555 if group_types.iter().enumerate().all(|(index, data_type)| {
556 index == window_index || matches!(data_type, DataType::Int64)
557 }) {
558 WindowGroupKeyKind::FastI64
559 } else {
560 WindowGroupKeyKind::Scalar
561 };
562 let use_batch_grouping = retractable_input
563 && matches!(&window, WindowSpec::Tumble { .. })
564 && group_exprs.len().saturating_sub(1) > 0;
565
566 let aggregate_exprs = aggregate.aggr_expr().to_vec();
567 let mut aggregate_kinds = Vec::with_capacity(aggregate_exprs.len());
568 for aggregate_expr in &aggregate_exprs {
569 let name = aggregate_expr.fun().name().to_ascii_lowercase();
570 if !matches!(name.as_str(), "count" | "sum" | "avg" | "min" | "max") {
571 return Err(DataFusionError::NotImplemented(format!(
572 "datum-sql windowed aggregation supports COUNT, SUM, AVG, MIN, and MAX for now, found {}",
573 aggregate_expr.fun().name()
574 )));
575 }
576 if aggregate_expr.is_distinct() {
577 return Err(DataFusionError::NotImplemented(
578 "datum-sql windowed aggregation does not support DISTINCT aggregates yet"
579 .into(),
580 ));
581 }
582 if !aggregate_expr.order_bys().is_empty() {
583 return Err(DataFusionError::NotImplemented(
584 "datum-sql windowed aggregation does not support ORDER BY aggregates yet"
585 .into(),
586 ));
587 }
588 if retractable_input {
589 aggregate_expr.create_sliding_accumulator()?;
590 }
591 aggregate_kinds.push(aggregate_kind(
592 aggregate_expr,
593 input_schema.as_ref(),
594 retractable_input,
595 )?);
596 }
597
598 Ok(Self {
599 output_schema: aggregate.schema(),
600 window,
601 group_types,
602 group_count: group_exprs.len(),
603 aggregate_count: aggregate_exprs.len(),
604 group_exprs,
605 aggregate_exprs,
606 aggregate_kinds,
607 key_kind,
608 use_batch_grouping,
609 retractable_input,
610 })
611 }
612
613 fn use_batch_grouping(&self) -> bool {
614 self.use_batch_grouping
615 }
616
617 fn prepare_batch(&self, batch: &RecordBatch) -> Result<PreparedBatch> {
618 let event_times = self
619 .window
620 .event_time_expr()
621 .evaluate(batch)?
622 .into_array(batch.num_rows())?;
623 let group_values = self
624 .group_exprs
625 .iter()
626 .map(|expr| expr.evaluate(batch)?.into_array(batch.num_rows()))
627 .collect::<Result<Vec<_>>>()?;
628 let aggregate_values = self
629 .aggregate_exprs
630 .iter()
631 .map(|aggregate_expr| {
632 aggregate_expr
633 .expressions()
634 .iter()
635 .map(|expr| expr.evaluate(batch)?.into_array(batch.num_rows()))
636 .collect::<Result<Vec<_>>>()
637 })
638 .collect::<Result<Vec<_>>>()?;
639 Ok(PreparedBatch {
640 event_times,
641 group_values,
642 aggregate_values,
643 })
644 }
645
646 fn group_key(
647 &self,
648 prepared: &PreparedBatch,
649 row: usize,
650 window_start_ns: i64,
651 ) -> Result<WindowGroupKey> {
652 match self.key_kind {
653 WindowGroupKeyKind::FastI64 => {
654 let mut values = FastI64GroupValues::None;
655 for (index, array) in prepared.group_values.iter().enumerate() {
656 if index == self.window.group_index() {
657 continue;
658 }
659 let array = array.as_any().downcast_ref::<Int64Array>().ok_or_else(|| {
660 DataFusionError::Internal(
661 "fast window group key expected Int64Array".into(),
662 )
663 })?;
664 if array.is_null(row) {
665 return Err(DataFusionError::Plan(format!(
666 "window group expression contains null at row {row}"
667 )));
668 }
669 values.push(array.value(row));
670 }
671 Ok(WindowGroupKey::FastI64 {
672 window_start_ns,
673 values,
674 })
675 }
676 WindowGroupKeyKind::Scalar => Ok(WindowGroupKey::Scalar(
677 prepared
678 .group_values
679 .iter()
680 .enumerate()
681 .map(|(index, array)| {
682 if index == self.window.group_index() {
683 timestamp_scalar_from_ns(window_start_ns, self.window.output_type())
684 } else {
685 ScalarValue::try_from_array(array.as_ref(), row)
686 }
687 })
688 .collect::<Result<Vec<_>>>()?,
689 )),
690 }
691 }
692
693 fn group_values_from_key(&self, key: &WindowGroupKey) -> Result<Vec<ScalarValue>> {
694 match key {
695 WindowGroupKey::FastI64 {
696 window_start_ns,
697 values,
698 } => {
699 let mut out = Vec::with_capacity(self.group_count);
700 let mut fast_index = 0;
701 for (index, data_type) in self.group_types.iter().enumerate() {
702 if index == self.window.group_index() {
703 out.push(timestamp_scalar_from_ns(
704 *window_start_ns,
705 self.window.output_type(),
706 )?);
707 } else {
708 let value = values.get(fast_index).ok_or_else(|| {
709 DataFusionError::Internal("fast group key value missing".into())
710 })?;
711 if !matches!(data_type, DataType::Int64) {
712 return Err(DataFusionError::Internal(
713 "fast group key carried a non-Int64 field".into(),
714 ));
715 }
716 out.push(ScalarValue::Int64(Some(value)));
717 fast_index += 1;
718 }
719 }
720 Ok(out)
721 }
722 WindowGroupKey::Scalar(values) => Ok(values.clone()),
723 }
724 }
725
726 fn create_aggregate_states(&self) -> Result<Vec<AggregateState>> {
727 self.aggregate_exprs
728 .iter()
729 .zip(&self.aggregate_kinds)
730 .map(|(expr, kind)| {
731 Ok(match kind {
732 AggregateKind::Count => AggregateState::Count { count: 0 },
733 AggregateKind::SumInt64 => AggregateState::SumInt64 { sum: 0, count: 0 },
734 AggregateKind::MinInt64 => AggregateState::MinInt64 { value: None },
735 AggregateKind::MaxInt64 => AggregateState::MaxInt64 { value: None },
736 AggregateKind::Generic => {
737 let accumulator = if self.retractable_input {
738 expr.create_sliding_accumulator()
739 } else {
740 expr.create_accumulator()
741 }?;
742 AggregateState::Generic(accumulator)
743 }
744 })
745 })
746 .collect()
747 }
748
749 fn apply_rows(
750 &self,
751 entry: &mut WindowEntry,
752 prepared: &PreparedBatch,
753 changes: &[(usize, ChangeOp)],
754 ) -> Result<()> {
755 for (aggregate_index, state) in entry.states.iter_mut().enumerate() {
756 state.apply_rows(&prepared.aggregate_values[aggregate_index], changes)?;
757 }
758 Ok(())
759 }
760
761 fn evaluate_entry(
762 &self,
763 key: &WindowGroupKey,
764 entry: &mut WindowEntry,
765 ) -> Result<Vec<ScalarValue>> {
766 let mut row = Vec::with_capacity(self.group_count + self.aggregate_count);
767 row.extend(self.group_values_from_key(key)?);
768 for state in &mut entry.states {
769 row.push(state.evaluate()?);
770 }
771 Ok(row)
772 }
773
774 fn build_output_batch(&self, rows: Vec<Vec<ScalarValue>>) -> Result<RecordBatch> {
775 let column_count = self.output_schema.fields().len();
776 let mut columns = vec![Vec::with_capacity(rows.len()); column_count];
777 for row in rows {
778 if row.len() != column_count {
779 return Err(DataFusionError::Internal(format!(
780 "windowed aggregate produced {} values for {column_count} output columns",
781 row.len()
782 )));
783 }
784 for (index, value) in row.into_iter().enumerate() {
785 columns[index].push(value);
786 }
787 }
788 let arrays = columns
789 .into_iter()
790 .map(ScalarValue::iter_to_array)
791 .collect::<Result<Vec<_>>>()?;
792 RecordBatch::try_new(Arc::clone(&self.output_schema), arrays).map_err(DataFusionError::from)
793 }
794}
795
796struct PreparedBatch {
797 event_times: ArrayRef,
798 group_values: Vec<ArrayRef>,
799 aggregate_values: Vec<Vec<ArrayRef>>,
800}
801
802struct PendingWindowGroup {
803 window_end_ns: i64,
804 changes: Vec<(usize, ChangeOp)>,
805}
806
807impl PendingWindowGroup {
808 fn new(window_end_ns: i64) -> Self {
809 Self {
810 window_end_ns,
811 changes: Vec::new(),
812 }
813 }
814}
815
816struct WindowEntry {
817 states: Vec<AggregateState>,
818}
819
820impl WindowEntry {
821 fn new(_window_end_ns: i64, states: Vec<AggregateState>) -> Self {
822 Self { states }
823 }
824}
825
826enum AggregateState {
827 Count { count: i64 },
828 SumInt64 { sum: i64, count: u64 },
829 MinInt64 { value: Option<i64> },
830 MaxInt64 { value: Option<i64> },
831 Generic(Box<dyn Accumulator>),
832}
833
834impl AggregateState {
835 fn apply_rows(&mut self, values: &[ArrayRef], changes: &[(usize, ChangeOp)]) -> Result<()> {
836 match self {
837 Self::Count { count } => {
838 for (row, change) in changes {
839 if row_is_counted(values, *row) {
840 if change.is_positive() {
841 *count += 1;
842 } else {
843 *count -= 1;
844 }
845 }
846 }
847 }
848 Self::SumInt64 { sum, count } => {
849 let array = single_int64_input(values, "SUM")?;
850 for (row, change) in changes {
851 if array.is_null(*row) {
852 continue;
853 }
854 let value = array.value(*row);
855 if change.is_positive() {
856 *sum = sum.wrapping_add(value);
857 *count += 1;
858 } else {
859 *sum = sum.wrapping_sub(value);
860 *count = count.saturating_sub(1);
861 }
862 }
863 }
864 Self::MinInt64 { value } => {
865 let array = single_int64_input(values, "MIN")?;
866 for (row, change) in changes {
867 if change.is_retraction() {
868 return Err(DataFusionError::Internal(
869 "fast MIN state cannot retract; generic sliding state should have been selected"
870 .into(),
871 ));
872 }
873 if !array.is_null(*row) {
874 let next = array.value(*row);
875 *value = Some(value.map_or(next, |current| current.min(next)));
876 }
877 }
878 }
879 Self::MaxInt64 { value } => {
880 let array = single_int64_input(values, "MAX")?;
881 for (row, change) in changes {
882 if change.is_retraction() {
883 return Err(DataFusionError::Internal(
884 "fast MAX state cannot retract; generic sliding state should have been selected"
885 .into(),
886 ));
887 }
888 if !array.is_null(*row) {
889 let next = array.value(*row);
890 *value = Some(value.map_or(next, |current| current.max(next)));
891 }
892 }
893 }
894 Self::Generic(accumulator) => {
895 for (row, change) in changes {
896 let values = values
897 .iter()
898 .map(|array| single_value_array(array.as_ref(), *row))
899 .collect::<Result<Vec<_>>>()?;
900 if change.is_positive() {
901 accumulator.update_batch(&values)?;
902 } else {
903 accumulator.retract_batch(&values)?;
904 }
905 }
906 }
907 }
908 Ok(())
909 }
910
911 fn evaluate(&mut self) -> Result<ScalarValue> {
912 match self {
913 Self::Count { count } => Ok(ScalarValue::Int64(Some(*count))),
914 Self::SumInt64 { sum, count } => {
915 if *count == 0 {
916 Ok(ScalarValue::Int64(None))
917 } else {
918 Ok(ScalarValue::Int64(Some(*sum)))
919 }
920 }
921 Self::MinInt64 { value } | Self::MaxInt64 { value } => Ok(ScalarValue::Int64(*value)),
922 Self::Generic(accumulator) => accumulator.evaluate(),
923 }
924 }
925
926 fn estimated_size(&self) -> usize {
927 match self {
928 Self::Count { .. } => mem::size_of::<Self>(),
929 Self::SumInt64 { .. } => mem::size_of::<Self>(),
930 Self::MinInt64 { .. } | Self::MaxInt64 { .. } => mem::size_of::<Self>(),
931 Self::Generic(accumulator) => mem::size_of::<Self>() + accumulator.size(),
932 }
933 }
934}
935
936#[derive(Clone, Copy)]
937enum AggregateKind {
938 Count,
939 SumInt64,
940 MinInt64,
941 MaxInt64,
942 Generic,
943}
944
945#[derive(Clone, Copy)]
946enum WindowGroupKeyKind {
947 FastI64,
948 Scalar,
949}
950
951#[derive(Clone, Eq, Hash, PartialEq)]
952enum WindowGroupKey {
953 FastI64 {
954 window_start_ns: i64,
955 values: FastI64GroupValues,
956 },
957 Scalar(Vec<ScalarValue>),
958}
959
960impl WindowGroupKey {
961 fn sort_cmp(left: &Self, right: &Self) -> CmpOrdering {
962 match (left, right) {
963 (
964 Self::FastI64 {
965 window_start_ns: left_window,
966 values: left_values,
967 },
968 Self::FastI64 {
969 window_start_ns: right_window,
970 values: right_values,
971 },
972 ) => left_window
973 .cmp(right_window)
974 .then_with(|| left_values.cmp(right_values)),
975 _ => left.sort_key().cmp(&right.sort_key()),
976 }
977 }
978
979 fn sort_key(&self) -> String {
980 match self {
981 Self::FastI64 {
982 window_start_ns,
983 values,
984 } => format!("{window_start_ns}|{}", values.sort_key()),
985 Self::Scalar(values) => values
986 .iter()
987 .map(ToString::to_string)
988 .collect::<Vec<_>>()
989 .join("|"),
990 }
991 }
992}
993
994#[derive(Clone, Eq, Hash, PartialEq)]
995enum FastI64GroupValues {
996 None,
997 One(i64),
998 Many(Vec<i64>),
999}
1000
1001impl FastI64GroupValues {
1002 fn push(&mut self, value: i64) {
1003 *self = match self {
1004 Self::None => Self::One(value),
1005 Self::One(current) => Self::Many(vec![*current, value]),
1006 Self::Many(values) => {
1007 values.push(value);
1008 return;
1009 }
1010 };
1011 }
1012
1013 fn get(&self, index: usize) -> Option<i64> {
1014 match (self, index) {
1015 (Self::None, _) => None,
1016 (Self::One(value), 0) => Some(*value),
1017 (Self::One(_), _) => None,
1018 (Self::Many(values), index) => values.get(index).copied(),
1019 }
1020 }
1021
1022 fn sort_key(&self) -> String {
1023 match self {
1024 Self::None => String::new(),
1025 Self::One(value) => value.to_string(),
1026 Self::Many(values) => values
1027 .iter()
1028 .map(ToString::to_string)
1029 .collect::<Vec<_>>()
1030 .join("|"),
1031 }
1032 }
1033}
1034
1035impl PartialOrd for FastI64GroupValues {
1036 fn partial_cmp(&self, other: &Self) -> Option<CmpOrdering> {
1037 Some(self.cmp(other))
1038 }
1039}
1040
1041impl Ord for FastI64GroupValues {
1042 fn cmp(&self, other: &Self) -> CmpOrdering {
1043 match (self, other) {
1044 (Self::None, Self::None) => CmpOrdering::Equal,
1045 (Self::None, _) => CmpOrdering::Less,
1046 (_, Self::None) => CmpOrdering::Greater,
1047 (Self::One(left), Self::One(right)) => left.cmp(right),
1048 (Self::One(left), Self::Many(right)) => {
1049 std::slice::from_ref(left).cmp(right.as_slice())
1050 }
1051 (Self::Many(left), Self::One(right)) => {
1052 left.as_slice().cmp(std::slice::from_ref(right))
1053 }
1054 (Self::Many(left), Self::Many(right)) => left.cmp(right),
1055 }
1056 }
1057}
1058
1059struct WindowAssignment {
1060 start_ns: i64,
1061 end_ns: i64,
1062}
1063
1064#[derive(Clone)]
1065enum WindowSpec {
1066 Tumble {
1067 group_index: usize,
1068 width_ns: i64,
1069 origin_ns: i64,
1070 event_time_expr: Arc<dyn PhysicalExpr>,
1071 output_type: DataType,
1072 },
1073 Hop {
1074 group_index: usize,
1075 slide_ns: i64,
1076 width_ns: i64,
1077 origin_ns: i64,
1078 event_time_expr: Arc<dyn PhysicalExpr>,
1079 output_type: DataType,
1080 },
1081}
1082
1083impl WindowSpec {
1084 fn event_time_expr(&self) -> &Arc<dyn PhysicalExpr> {
1085 match self {
1086 Self::Tumble {
1087 event_time_expr, ..
1088 }
1089 | Self::Hop {
1090 event_time_expr, ..
1091 } => event_time_expr,
1092 }
1093 }
1094
1095 fn output_type(&self) -> &DataType {
1096 match self {
1097 Self::Tumble { output_type, .. } | Self::Hop { output_type, .. } => output_type,
1098 }
1099 }
1100
1101 fn group_index(&self) -> usize {
1102 match self {
1103 Self::Tumble { group_index, .. } | Self::Hop { group_index, .. } => *group_index,
1104 }
1105 }
1106
1107 fn assignments_into(&self, event_time_ns: i64, out: &mut Vec<WindowAssignment>) -> Result<()> {
1108 out.clear();
1109 match self {
1110 Self::Tumble {
1111 width_ns,
1112 origin_ns,
1113 ..
1114 } => {
1115 let start_ns = floor_to_origin(event_time_ns, *width_ns, *origin_ns)?;
1116 out.push(WindowAssignment {
1117 start_ns,
1118 end_ns: checked_add_ns(start_ns, *width_ns)?,
1119 });
1120 Ok(())
1121 }
1122 Self::Hop {
1123 slide_ns,
1124 width_ns,
1125 origin_ns,
1126 ..
1127 } => {
1128 let latest_start = floor_to_origin(event_time_ns, *slide_ns, *origin_ns)?;
1129 let hop_count = width_ns.checked_div(*slide_ns).ok_or_else(|| {
1130 DataFusionError::Plan("hop slide interval cannot be zero".into())
1131 })?;
1132 out.reserve(hop_count as usize);
1133 for hop in 0..hop_count {
1134 let start_ns = latest_start
1135 .checked_sub(hop.checked_mul(*slide_ns).ok_or_else(|| {
1136 DataFusionError::Plan(
1137 "hop assignment underflowed i64 nanoseconds".into(),
1138 )
1139 })?)
1140 .ok_or_else(|| {
1141 DataFusionError::Plan(
1142 "hop assignment underflowed i64 nanoseconds".into(),
1143 )
1144 })?;
1145 let end_ns = checked_add_ns(start_ns, *width_ns)?;
1146 if event_time_ns >= start_ns && event_time_ns < end_ns {
1147 out.push(WindowAssignment { start_ns, end_ns });
1148 }
1149 }
1150 Ok(())
1151 }
1152 }
1153 }
1154}
1155
1156fn aggregate_kind(
1157 aggregate_expr: &datafusion::physical_expr::aggregate::AggregateFunctionExpr,
1158 input_schema: &Schema,
1159 retractable_input: bool,
1160) -> Result<AggregateKind> {
1161 let name = aggregate_expr.fun().name().to_ascii_lowercase();
1162 match name.as_str() {
1163 "count" => Ok(AggregateKind::Count),
1164 "sum" => {
1165 if aggregate_expr.expressions().len() == 1
1166 && aggregate_expr.expressions()[0].data_type(input_schema)? == DataType::Int64
1167 && aggregate_expr.field().data_type() == &DataType::Int64
1168 {
1169 Ok(AggregateKind::SumInt64)
1170 } else {
1171 Ok(AggregateKind::Generic)
1172 }
1173 }
1174 "min" => {
1175 if !retractable_input
1176 && aggregate_expr.expressions().len() == 1
1177 && aggregate_expr.expressions()[0].data_type(input_schema)? == DataType::Int64
1178 && aggregate_expr.field().data_type() == &DataType::Int64
1179 {
1180 Ok(AggregateKind::MinInt64)
1181 } else {
1182 Ok(AggregateKind::Generic)
1183 }
1184 }
1185 "max" => {
1186 if !retractable_input
1187 && aggregate_expr.expressions().len() == 1
1188 && aggregate_expr.expressions()[0].data_type(input_schema)? == DataType::Int64
1189 && aggregate_expr.field().data_type() == &DataType::Int64
1190 {
1191 Ok(AggregateKind::MaxInt64)
1192 } else {
1193 Ok(AggregateKind::Generic)
1194 }
1195 }
1196 _ => Ok(AggregateKind::Generic),
1197 }
1198}
1199
1200fn find_window_spec(
1201 group_exprs: &[Arc<dyn PhysicalExpr>],
1202 input_schema: &Schema,
1203) -> Result<(usize, WindowSpec)> {
1204 let mut found = None;
1205 for (index, expr) in group_exprs.iter().enumerate() {
1206 if let Some(spec) = window_spec_from_expr(index, expr, input_schema)? {
1207 if found.is_some() {
1208 return Err(DataFusionError::NotImplemented(
1209 "datum-sql windowed aggregation supports exactly one window group expression"
1210 .into(),
1211 ));
1212 }
1213 found = Some((index, spec));
1214 }
1215 }
1216 found.ok_or_else(|| {
1217 DataFusionError::NotImplemented(
1218 "datum-sql windowed aggregation requires GROUP BY date_bin(...), tumble(...), or hop(...)"
1219 .into(),
1220 )
1221 })
1222}
1223
1224fn window_spec_from_expr(
1225 group_index: usize,
1226 expr: &Arc<dyn PhysicalExpr>,
1227 input_schema: &Schema,
1228) -> Result<Option<WindowSpec>> {
1229 let Some(function) = expr.downcast_ref::<ScalarFunctionExpr>() else {
1230 return Ok(None);
1231 };
1232 match function.name().to_ascii_lowercase().as_str() {
1233 "date_bin" => {
1234 if function.args().len() != 2 {
1235 return Err(DataFusionError::Plan(format!(
1236 "date_bin window grouping expects two arguments in WP-SQL-3' lowering, found {}",
1237 function.args().len()
1238 )));
1239 }
1240 let width_ns = fixed_interval_ns(&function.args()[0])?;
1241 let output_type = expr.data_type(input_schema)?;
1242 validate_timestamp_output_type(&output_type, "date_bin")?;
1243 Ok(Some(WindowSpec::Tumble {
1244 group_index,
1245 width_ns,
1246 origin_ns: 0,
1247 event_time_expr: Arc::clone(&function.args()[1]),
1248 output_type,
1249 }))
1250 }
1251 "tumble" => {
1252 if function.args().len() != 2 {
1253 return Err(DataFusionError::Plan(format!(
1254 "tumble window grouping expects two arguments, found {}",
1255 function.args().len()
1256 )));
1257 }
1258 let width_ns = fixed_interval_ns(&function.args()[0])?;
1259 let output_type = expr.data_type(input_schema)?;
1260 validate_timestamp_output_type(&output_type, "tumble")?;
1261 Ok(Some(WindowSpec::Tumble {
1262 group_index,
1263 width_ns,
1264 origin_ns: 0,
1265 event_time_expr: Arc::clone(&function.args()[1]),
1266 output_type,
1267 }))
1268 }
1269 "hop" => {
1270 if function.args().len() != 3 {
1271 return Err(DataFusionError::Plan(format!(
1272 "hop window grouping expects three arguments, found {}",
1273 function.args().len()
1274 )));
1275 }
1276 let slide_ns = fixed_interval_ns(&function.args()[0])?;
1277 let width_ns = fixed_interval_ns(&function.args()[1])?;
1278 if width_ns % slide_ns != 0 {
1279 return Err(DataFusionError::Plan(format!(
1280 "hop window width {width_ns}ns must be an integer multiple of slide {slide_ns}ns"
1281 )));
1282 }
1283 let output_type = expr.data_type(input_schema)?;
1284 validate_timestamp_output_type(&output_type, "hop")?;
1285 Ok(Some(WindowSpec::Hop {
1286 group_index,
1287 slide_ns,
1288 width_ns,
1289 origin_ns: 0,
1290 event_time_expr: Arc::clone(&function.args()[2]),
1291 output_type,
1292 }))
1293 }
1294 _ => Ok(None),
1295 }
1296}
1297
1298fn fixed_interval_ns(expr: &Arc<dyn PhysicalExpr>) -> Result<i64> {
1299 let literal = expr.downcast_ref::<Literal>().ok_or_else(|| {
1300 DataFusionError::Plan(
1301 "window interval must be a literal INTERVAL for WP-SQL-3' lowering".into(),
1302 )
1303 })?;
1304 let nanos = match literal.value() {
1305 ScalarValue::IntervalDayTime(Some(value)) => {
1306 let (days, millis) = IntervalDayTimeType::to_parts(*value);
1307 i64::from(days)
1308 .checked_mul(86_400_000_000_000)
1309 .and_then(|base| base.checked_add(i64::from(millis) * 1_000_000))
1310 .ok_or_else(|| {
1311 DataFusionError::Plan("window interval overflowed i64 nanoseconds".into())
1312 })?
1313 }
1314 ScalarValue::IntervalMonthDayNano(Some(value)) => {
1315 let (months, days, nanos) = IntervalMonthDayNanoType::to_parts(*value);
1316 if months != 0 {
1317 return Err(DataFusionError::NotImplemented(
1318 "datum-sql windowed aggregation supports fixed day/time intervals only, not month intervals"
1319 .into(),
1320 ));
1321 }
1322 i64::from(days)
1323 .checked_mul(86_400_000_000_000)
1324 .and_then(|base| base.checked_add(nanos))
1325 .ok_or_else(|| {
1326 DataFusionError::Plan("window interval overflowed i64 nanoseconds".into())
1327 })?
1328 }
1329 ScalarValue::DurationSecond(Some(value)) => {
1330 value.checked_mul(1_000_000_000).ok_or_else(|| {
1331 DataFusionError::Plan("window interval overflowed i64 nanoseconds".into())
1332 })?
1333 }
1334 ScalarValue::DurationMillisecond(Some(value)) => {
1335 value.checked_mul(1_000_000).ok_or_else(|| {
1336 DataFusionError::Plan("window interval overflowed i64 nanoseconds".into())
1337 })?
1338 }
1339 ScalarValue::DurationMicrosecond(Some(value)) => {
1340 value.checked_mul(1_000).ok_or_else(|| {
1341 DataFusionError::Plan("window interval overflowed i64 nanoseconds".into())
1342 })?
1343 }
1344 ScalarValue::DurationNanosecond(Some(value)) => *value,
1345 other => {
1346 return Err(DataFusionError::Plan(format!(
1347 "window interval must be a non-null fixed interval, found {other:?}"
1348 )));
1349 }
1350 };
1351 if nanos <= 0 {
1352 return Err(DataFusionError::Plan(format!(
1353 "window interval must be positive, found {nanos}ns"
1354 )));
1355 }
1356 Ok(nanos)
1357}
1358
1359fn validate_timestamp_output_type(data_type: &DataType, function: &str) -> Result<()> {
1360 if matches!(data_type, DataType::Timestamp(_, _)) {
1361 Ok(())
1362 } else {
1363 Err(DataFusionError::Plan(format!(
1364 "{function} window grouping must return a timestamp, found {data_type:?}"
1365 )))
1366 }
1367}
1368
1369fn floor_to_origin(value: i64, stride: i64, origin: i64) -> Result<i64> {
1370 let diff = value.checked_sub(origin).ok_or_else(|| {
1371 DataFusionError::Plan("window assignment overflowed i64 nanoseconds".into())
1372 })?;
1373 let mut delta = diff - (diff % stride);
1374 if diff < 0 && stride > 1 && delta != diff {
1375 delta -= stride;
1376 }
1377 origin
1378 .checked_add(delta)
1379 .ok_or_else(|| DataFusionError::Plan("window assignment overflowed i64 nanoseconds".into()))
1380}
1381
1382fn checked_add_ns(left: i64, right: i64) -> Result<i64> {
1383 left.checked_add(right)
1384 .ok_or_else(|| DataFusionError::Plan("window end overflowed i64 nanoseconds".into()))
1385}
1386
1387fn single_value_array(array: &dyn Array, row: usize) -> Result<ArrayRef> {
1388 ScalarValue::iter_to_array([ScalarValue::try_from_array(array, row)?])
1389}
1390
1391fn row_is_counted(values: &[ArrayRef], row: usize) -> bool {
1392 values.iter().all(|array| !array.is_null(row))
1393}
1394
1395fn single_int64_input<'a>(values: &'a [ArrayRef], function: &str) -> Result<&'a Int64Array> {
1396 if values.len() != 1 {
1397 return Err(DataFusionError::Internal(format!(
1398 "fast {function} state expected one input array, found {}",
1399 values.len()
1400 )));
1401 }
1402 values[0]
1403 .as_any()
1404 .downcast_ref::<Int64Array>()
1405 .ok_or_else(|| {
1406 DataFusionError::Internal(format!("fast {function} state expected Int64Array"))
1407 })
1408}
1409
1410fn timestamp_ns_from_array(array: &ArrayRef, row: usize) -> Result<i64> {
1411 match array.data_type() {
1412 DataType::Timestamp(TimeUnit::Second, _) => timestamp_value_ns::<TimestampSecondArray>(
1413 array.as_any().downcast_ref::<TimestampSecondArray>(),
1414 row,
1415 1_000_000_000,
1416 ),
1417 DataType::Timestamp(TimeUnit::Millisecond, _) => {
1418 timestamp_value_ns::<TimestampMillisecondArray>(
1419 array.as_any().downcast_ref::<TimestampMillisecondArray>(),
1420 row,
1421 1_000_000,
1422 )
1423 }
1424 DataType::Timestamp(TimeUnit::Microsecond, _) => {
1425 timestamp_value_ns::<TimestampMicrosecondArray>(
1426 array.as_any().downcast_ref::<TimestampMicrosecondArray>(),
1427 row,
1428 1_000,
1429 )
1430 }
1431 DataType::Timestamp(TimeUnit::Nanosecond, _) => {
1432 timestamp_value_ns::<TimestampNanosecondArray>(
1433 array.as_any().downcast_ref::<TimestampNanosecondArray>(),
1434 row,
1435 1,
1436 )
1437 }
1438 other => Err(DataFusionError::Plan(format!(
1439 "window event-time expression must evaluate to Timestamp, found {other:?}"
1440 ))),
1441 }
1442}
1443
1444fn timestamp_value_ns<T>(array: Option<&T>, row: usize, multiplier: i64) -> Result<i64>
1445where
1446 T: Array + TimestampArrayValue,
1447{
1448 let array =
1449 array.ok_or_else(|| DataFusionError::Internal("timestamp array type mismatch".into()))?;
1450 if array.is_null(row) {
1451 return Err(DataFusionError::Plan(format!(
1452 "window event-time expression contains null at row {row}"
1453 )));
1454 }
1455 array
1456 .timestamp_value(row)
1457 .checked_mul(multiplier)
1458 .ok_or_else(|| DataFusionError::Plan("timestamp overflowed i64 nanoseconds".into()))
1459}
1460
1461trait TimestampArrayValue {
1462 fn timestamp_value(&self, row: usize) -> i64;
1463}
1464
1465impl TimestampArrayValue for TimestampSecondArray {
1466 fn timestamp_value(&self, row: usize) -> i64 {
1467 self.value(row)
1468 }
1469}
1470
1471impl TimestampArrayValue for TimestampMillisecondArray {
1472 fn timestamp_value(&self, row: usize) -> i64 {
1473 self.value(row)
1474 }
1475}
1476
1477impl TimestampArrayValue for TimestampMicrosecondArray {
1478 fn timestamp_value(&self, row: usize) -> i64 {
1479 self.value(row)
1480 }
1481}
1482
1483impl TimestampArrayValue for TimestampNanosecondArray {
1484 fn timestamp_value(&self, row: usize) -> i64 {
1485 self.value(row)
1486 }
1487}
1488
1489fn timestamp_scalar_from_ns(timestamp_ns: i64, data_type: &DataType) -> Result<ScalarValue> {
1490 match data_type {
1491 DataType::Timestamp(TimeUnit::Second, timezone) => Ok(ScalarValue::TimestampSecond(
1492 checked_unit_value(timestamp_ns, 1_000_000_000)?,
1493 timezone.clone(),
1494 )),
1495 DataType::Timestamp(TimeUnit::Millisecond, timezone) => {
1496 Ok(ScalarValue::TimestampMillisecond(
1497 checked_unit_value(timestamp_ns, 1_000_000)?,
1498 timezone.clone(),
1499 ))
1500 }
1501 DataType::Timestamp(TimeUnit::Microsecond, timezone) => {
1502 Ok(ScalarValue::TimestampMicrosecond(
1503 checked_unit_value(timestamp_ns, 1_000)?,
1504 timezone.clone(),
1505 ))
1506 }
1507 DataType::Timestamp(TimeUnit::Nanosecond, timezone) => Ok(
1508 ScalarValue::TimestampNanosecond(Some(timestamp_ns), timezone.clone()),
1509 ),
1510 other => Err(DataFusionError::Plan(format!(
1511 "window start output type must be Timestamp, found {other:?}"
1512 ))),
1513 }
1514}
1515
1516fn checked_unit_value(timestamp_ns: i64, divisor: i64) -> Result<Option<i64>> {
1517 if timestamp_ns % divisor != 0 {
1518 return Err(DataFusionError::Plan(format!(
1519 "window start {timestamp_ns}ns cannot be represented in timestamp unit divisor {divisor}"
1520 )));
1521 }
1522 Ok(Some(timestamp_ns / divisor))
1523}