datafusion_physical_expr/window/
aggregate.rs1use std::any::Any;
21use std::ops::Range;
22use std::sync::Arc;
23
24use crate::aggregate::AggregateFunctionExpr;
25use crate::window::standard::add_new_ordering_expr_with_partition_by;
26use crate::window::window_expr::{
27 AggregateWindowExpr, WindowEvalContext, WindowFn, filter_array,
28};
29use crate::window::{
30 PartitionBatches, PartitionWindowAggStates, SlidingAggregateWindowExpr, WindowExpr,
31};
32use crate::{EquivalenceProperties, PhysicalExpr};
33
34use arrow::array::ArrayRef;
35use arrow::array::BooleanArray;
36use arrow::datatypes::FieldRef;
37use arrow::record_batch::RecordBatch;
38use datafusion_common::{Result, ScalarValue, exec_datafusion_err};
39use datafusion_expr::{Accumulator, WindowFrame, WindowFrameBound, WindowFrameUnits};
40use datafusion_physical_expr_common::sort_expr::PhysicalSortExpr;
41
42#[derive(Debug)]
46pub struct PlainAggregateWindowExpr {
47 aggregate: Arc<AggregateFunctionExpr>,
48 partition_by: Vec<Arc<dyn PhysicalExpr>>,
49 order_by: Vec<PhysicalSortExpr>,
50 window_frame: Arc<WindowFrame>,
51 is_constant_in_partition: bool,
52 filter: Option<Arc<dyn PhysicalExpr>>,
53}
54
55impl PlainAggregateWindowExpr {
56 pub fn new(
58 aggregate: Arc<AggregateFunctionExpr>,
59 partition_by: &[Arc<dyn PhysicalExpr>],
60 order_by: &[PhysicalSortExpr],
61 window_frame: Arc<WindowFrame>,
62 filter: Option<Arc<dyn PhysicalExpr>>,
63 ) -> Self {
64 let is_constant_in_partition =
65 Self::is_window_constant_in_partition(order_by, &window_frame);
66 Self {
67 aggregate,
68 partition_by: partition_by.to_vec(),
69 order_by: order_by.to_vec(),
70 window_frame,
71 is_constant_in_partition,
72 filter,
73 }
74 }
75
76 pub fn get_aggregate_expr(&self) -> &AggregateFunctionExpr {
78 &self.aggregate
79 }
80
81 pub fn add_equal_orderings(
82 &self,
83 eq_properties: &mut EquivalenceProperties,
84 window_expr_index: usize,
85 ) -> Result<()> {
86 if let Some(expr) = self
87 .get_aggregate_expr()
88 .get_result_ordering(window_expr_index)
89 {
90 add_new_ordering_expr_with_partition_by(
91 eq_properties,
92 expr,
93 &self.partition_by,
94 )?;
95 }
96 Ok(())
97 }
98
99 fn is_window_constant_in_partition(
109 order_by: &[PhysicalSortExpr],
110 window_frame: &WindowFrame,
111 ) -> bool {
112 let is_constant_bound = |bound: &WindowFrameBound| match bound {
113 WindowFrameBound::CurrentRow => {
114 window_frame.units == WindowFrameUnits::Range && order_by.is_empty()
115 }
116 _ => bound.is_unbounded(),
117 };
118
119 is_constant_bound(&window_frame.start_bound)
120 && is_constant_bound(&window_frame.end_bound)
121 }
122}
123
124impl WindowExpr for PlainAggregateWindowExpr {
128 fn as_any(&self) -> &dyn Any {
130 self
131 }
132
133 fn field(&self) -> Result<FieldRef> {
134 Ok(self.aggregate.field())
135 }
136
137 fn name(&self) -> &str {
138 self.aggregate.name()
139 }
140
141 fn expressions(&self) -> Vec<Arc<dyn PhysicalExpr>> {
142 self.aggregate.expressions()
143 }
144
145 fn evaluate(&self, batch: &RecordBatch) -> Result<ArrayRef> {
146 self.aggregate_evaluate(batch)
147 }
148
149 fn evaluate_stateful(
150 &self,
151 partition_batches: &PartitionBatches,
152 window_agg_state: &mut PartitionWindowAggStates,
153 eval_ctx: &WindowEvalContext<'_>,
154 ) -> Result<()> {
155 self.aggregate_evaluate_stateful(partition_batches, window_agg_state, eval_ctx)?;
156
157 for partition_row in partition_batches.keys() {
163 let window_state = window_agg_state
164 .get_mut(partition_row)
165 .ok_or_else(|| exec_datafusion_err!("Cannot find state"))?;
166 let state = &mut window_state.state;
167 if self.window_frame.start_bound.is_unbounded() {
168 state.window_frame_range.start =
169 state.window_frame_range.end.saturating_sub(1);
170 }
171 }
172 Ok(())
173 }
174
175 fn partition_by(&self) -> &[Arc<dyn PhysicalExpr>] {
176 &self.partition_by
177 }
178
179 fn order_by(&self) -> &[PhysicalSortExpr] {
180 &self.order_by
181 }
182
183 fn get_window_frame(&self) -> &Arc<WindowFrame> {
184 &self.window_frame
185 }
186
187 fn get_reverse_expr(&self) -> Option<Arc<dyn WindowExpr>> {
188 self.aggregate.reverse_expr().map(|reverse_expr| {
189 let reverse_window_frame = self.window_frame.reverse();
190 if reverse_window_frame.is_ever_expanding() {
191 Arc::new(PlainAggregateWindowExpr::new(
192 Arc::new(reverse_expr),
193 &self.partition_by.clone(),
194 &self
195 .order_by
196 .iter()
197 .map(|e| e.reverse())
198 .collect::<Vec<_>>(),
199 Arc::new(self.window_frame.reverse()),
200 self.filter.clone(),
201 )) as _
202 } else {
203 Arc::new(SlidingAggregateWindowExpr::new(
204 Arc::new(reverse_expr),
205 &self.partition_by.clone(),
206 &self
207 .order_by
208 .iter()
209 .map(|e| e.reverse())
210 .collect::<Vec<_>>(),
211 Arc::new(self.window_frame.reverse()),
212 self.filter.clone(),
213 )) as _
214 }
215 })
216 }
217
218 fn uses_bounded_memory(&self) -> bool {
219 !self.window_frame.end_bound.is_unbounded()
220 }
221
222 fn create_window_fn(&self) -> Result<WindowFn> {
223 Ok(WindowFn::Aggregate(self.get_accumulator()?))
224 }
225}
226
227impl AggregateWindowExpr for PlainAggregateWindowExpr {
228 fn get_accumulator(&self) -> Result<Box<dyn Accumulator>> {
229 self.aggregate.create_accumulator()
230 }
231
232 fn filter_expr(&self) -> Option<&Arc<dyn PhysicalExpr>> {
233 self.filter.as_ref()
234 }
235
236 fn get_aggregate_result_inside_range(
242 &self,
243 last_range: &Range<usize>,
244 cur_range: &Range<usize>,
245 value_slice: &[ArrayRef],
246 accumulator: &mut Box<dyn Accumulator>,
247 filter_mask: Option<&BooleanArray>,
248 ) -> Result<ScalarValue> {
249 if cur_range.start == cur_range.end {
250 self.aggregate
251 .default_value(self.aggregate.field().data_type())
252 } else {
253 let update_bound = cur_range.end - last_range.end;
255 if update_bound > 0 {
260 let slice_mask =
261 filter_mask.map(|m| m.slice(last_range.end, update_bound));
262 let update: Vec<ArrayRef> = value_slice
263 .iter()
264 .map(|v| v.slice(last_range.end, update_bound))
265 .map(|arr| match &slice_mask {
266 Some(m) => filter_array(&arr, m),
267 None => Ok(arr),
268 })
269 .collect::<Result<Vec<_>>>()?;
270 accumulator.update_batch(&update)?
271 }
272 accumulator.evaluate()
273 }
274 }
275
276 fn is_constant_in_partition(&self) -> bool {
277 self.is_constant_in_partition
278 }
279}