Skip to main content

datum_sql/
window.rs

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/// Counters maintained by a windowed aggregation stage.
27#[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}