blob: a0f0988b4f4e5e93d341c25db52c9bd4aa6c06cc [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! Partition evaluation module
use arrow::array::ArrayRef;
use datafusion_common::{exec_err, not_impl_err, Result, ScalarValue};
use std::fmt::Debug;
use std::ops::Range;
use crate::window_state::WindowAggState;
/// Partition evaluator for Window Functions
///
/// # Background
///
/// An implementation of this trait is created and used for each
/// partition defined by an `OVER` clause and is instantiated by
/// the DataFusion runtime.
///
/// For example, evaluating `window_func(val) OVER (PARTITION BY col)`
/// on the following data:
///
/// ```text
/// col | val
/// --- + ----
/// A | 10
/// A | 10
/// C | 20
/// D | 30
/// D | 30
/// ```
///
/// Will instantiate three `PartitionEvaluator`s, one each for the
/// partitions defined by `col=A`, `col=B`, and `col=C`.
///
/// ```text
/// col | val
/// --- + ----
/// A | 10 <--- partition 1
/// A | 10
///
/// col | val
/// --- + ----
/// C | 20 <--- partition 2
///
/// col | val
/// --- + ----
/// D | 30 <--- partition 3
/// D | 30
/// ```
///
/// Different methods on this trait will be called depending on the
/// capabilities described by [`supports_bounded_execution`],
/// [`uses_window_frame`], and [`include_rank`],
///
/// When implementing a new `PartitionEvaluator`, implement
/// corresponding evaluator according to table below.
///
/// # Implementation Table
///
/// |[`uses_window_frame`]|[`supports_bounded_execution`]|[`include_rank`]|function_to_implement|
/// |---|---|----|----|
/// |false (default) |false (default) |false (default) | [`evaluate_all`] |
/// |false |true |false | [`evaluate`] |
/// |false |true/false |true | [`evaluate_all_with_rank`] |
/// |true |true/false |true/false | [`evaluate`] |
///
/// [`evaluate`]: Self::evaluate
/// [`evaluate_all`]: Self::evaluate_all
/// [`evaluate_all_with_rank`]: Self::evaluate_all_with_rank
/// [`uses_window_frame`]: Self::uses_window_frame
/// [`include_rank`]: Self::include_rank
/// [`supports_bounded_execution`]: Self::supports_bounded_execution
pub trait PartitionEvaluator: Debug + Send {
/// When the window frame has a fixed beginning (e.g UNBOUNDED
/// PRECEDING), some functions such as FIRST_VALUE, LAST_VALUE and
/// NTH_VALUE do not need the (unbounded) input once they have
/// seen a certain amount of input.
///
/// `memoize` is called after each input batch is processed, and
/// such functions can save whatever they need and modify
/// [`WindowAggState`] appropriately to allow rows to be pruned
fn memoize(&mut self, _state: &mut WindowAggState) -> Result<()> {
Ok(())
}
/// If `uses_window_frame` flag is `false`. This method is used to
/// calculate required range for the window function during
/// stateful execution.
///
/// Generally there is no required range, hence by default this
/// returns smallest range(current row). e.g seeing current row is
/// enough to calculate window result (such as row_number, rank,
/// etc)
fn get_range(&self, idx: usize, _n_rows: usize) -> Result<Range<usize>> {
if self.uses_window_frame() {
exec_err!("Range should be calculated from window frame")
} else {
Ok(Range {
start: idx,
end: idx + 1,
})
}
}
/// Get whether evaluator needs future data for its result (if so returns `false`) or not
fn is_causal(&self) -> bool {
false
}
/// Evaluate a window function on an entire input partition.
///
/// This function is called once per input *partition* for window
/// functions that *do not use* values from the window frame,
/// such as `ROW_NUMBER`, `RANK`, `DENSE_RANK`, `PERCENT_RANK`,
/// `CUME_DIST`, `LEAD`, `LAG`).
///
/// It produces the result of all rows in a single pass. It
/// expects to receive the entire partition as the `value` and
/// must produce an output column with one output row for every
/// input row.
///
/// `num_rows` is required to correctly compute the output in case
/// `values.len() == 0`
///
/// Implementing this function is an optimization: certain window
/// functions are not affected by the window frame definition or
/// the query doesn't have a frame, and `evaluate` skips the
/// (costly) window frame boundary calculation and the overhead of
/// calling `evaluate` for each output row.
///
/// For example, the `LAG` built in window function does not use
/// the values of its window frame (it can be computed in one shot
/// on the entire partition with `Self::evaluate_all` regardless of the
/// window defined in the `OVER` clause)
///
/// ```sql
/// lag(x, 1) OVER (ORDER BY z ROWS BETWEEN 2 PRECEDING AND 3 FOLLOWING)
/// ```
///
/// However, `avg()` computes the average in the window and thus
/// does use its window frame
///
/// ```sql
/// avg(x) OVER (PARTITION BY y ORDER BY z ROWS BETWEEN 2 PRECEDING AND 3 FOLLOWING)
/// ```
fn evaluate_all(&mut self, values: &[ArrayRef], num_rows: usize) -> Result<ArrayRef> {
// When window frame boundaries are not used and evaluator supports bounded execution
// We can calculate evaluate result by repeatedly calling `self.evaluate` `num_rows` times
// If user wants to implement more efficient version, this method should be overwritten
// Default implementation may behave suboptimally (For instance `NumRowEvaluator` overwrites it)
if !self.uses_window_frame() && self.supports_bounded_execution() {
let res = (0..num_rows)
.map(|idx| self.evaluate(values, &self.get_range(idx, num_rows)?))
.collect::<Result<Vec<_>>>()?;
ScalarValue::iter_to_array(res)
} else {
not_impl_err!("evaluate_all is not implemented by default")
}
}
/// Evaluate window function on a range of rows in an input
/// partition.x
///
/// This is the simplest and most general function to implement
/// but also the least performant as it creates output one row at
/// a time. It is typically much faster to implement stateful
/// evaluation using one of the other specialized methods on this
/// trait.
///
/// Returns a [`ScalarValue`] that is the value of the window
/// function within `range` for the entire partition. Argument
/// `values` contains the evaluation result of function arguments
/// and evaluation results of ORDER BY expressions. If function has a
/// single argument, `values[1..]` will contain ORDER BY expression results.
fn evaluate(
&mut self,
_values: &[ArrayRef],
_range: &Range<usize>,
) -> Result<ScalarValue> {
not_impl_err!("evaluate is not implemented by default")
}
/// [`PartitionEvaluator::evaluate_all_with_rank`] is called for window
/// functions that only need the rank of a row within its window
/// frame.
///
/// Evaluate the partition evaluator against the partition using
/// the row ranks. For example, `RANK(col)` produces
///
/// ```text
/// col | rank
/// --- + ----
/// A | 1
/// A | 1
/// C | 3
/// D | 4
/// D | 5
/// ```
///
/// For this case, `num_rows` would be `5` and the
/// `ranks_in_partition` would be called with
///
/// ```text
/// [
/// (0,1),
/// (2,2),
/// (3,4),
/// ]
/// ```
fn evaluate_all_with_rank(
&self,
_num_rows: usize,
_ranks_in_partition: &[Range<usize>],
) -> Result<ArrayRef> {
not_impl_err!("evaluate_partition_with_rank is not implemented by default")
}
/// Can the window function be incrementally computed using
/// bounded memory?
///
/// See the table on [`Self`] for what functions to implement
fn supports_bounded_execution(&self) -> bool {
false
}
/// Does the window function use the values from the window frame,
/// if one is specified?
///
/// See the table on [`Self`] for what functions to implement
fn uses_window_frame(&self) -> bool {
false
}
/// Can this function be evaluated with (only) rank
///
/// See the table on [`Self`] for what functions to implement
fn include_rank(&self) -> bool {
false
}
}