| // 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. |
| |
| //! Structures used to hold window function state (for implementing WindowUDFs) |
| |
| use std::{collections::VecDeque, ops::Range, sync::Arc}; |
| |
| use crate::{WindowFrame, WindowFrameBound, WindowFrameUnits}; |
| |
| use arrow::{ |
| array::ArrayRef, |
| compute::{concat, concat_batches, SortOptions}, |
| datatypes::{DataType, SchemaRef}, |
| record_batch::RecordBatch, |
| }; |
| use datafusion_common::{ |
| internal_datafusion_err, internal_err, |
| utils::{compare_rows, get_row_at_idx, search_in_slice}, |
| Result, ScalarValue, |
| }; |
| |
| /// Holds the state of evaluating a window function |
| #[derive(Debug, Clone)] |
| pub struct WindowAggState { |
| /// The range that we calculate the window function |
| pub window_frame_range: Range<usize>, |
| pub window_frame_ctx: Option<WindowFrameContext>, |
| /// The index of the last row that its result is calculated inside the partition record batch buffer. |
| pub last_calculated_index: usize, |
| /// The offset of the deleted row number |
| pub offset_pruned_rows: usize, |
| /// Stores the results calculated by window frame |
| pub out_col: ArrayRef, |
| /// Keeps track of how many rows should be generated to be in sync with input record_batch. |
| // (For each row in the input record batch we need to generate a window result). |
| pub n_row_result_missing: usize, |
| /// Flag indicating whether we have received all data for this partition |
| pub is_end: bool, |
| } |
| |
| impl WindowAggState { |
| pub fn prune_state(&mut self, n_prune: usize) { |
| self.window_frame_range = Range { |
| start: self.window_frame_range.start - n_prune, |
| end: self.window_frame_range.end - n_prune, |
| }; |
| self.last_calculated_index -= n_prune; |
| self.offset_pruned_rows += n_prune; |
| |
| match self.window_frame_ctx.as_mut() { |
| // Rows have no state do nothing |
| Some(WindowFrameContext::Rows(_)) => {} |
| Some(WindowFrameContext::Range { .. }) => {} |
| Some(WindowFrameContext::Groups { state, .. }) => { |
| let mut n_group_to_del = 0; |
| for (_, end_idx) in &state.group_end_indices { |
| if n_prune < *end_idx { |
| break; |
| } |
| n_group_to_del += 1; |
| } |
| state.group_end_indices.drain(0..n_group_to_del); |
| state |
| .group_end_indices |
| .iter_mut() |
| .for_each(|(_, start_idx)| *start_idx -= n_prune); |
| state.current_group_idx -= n_group_to_del; |
| } |
| None => {} |
| }; |
| } |
| |
| pub fn update( |
| &mut self, |
| out_col: &ArrayRef, |
| partition_batch_state: &PartitionBatchState, |
| ) -> Result<()> { |
| self.last_calculated_index += out_col.len(); |
| // no need to use concat if the current `out_col` is empty |
| if self.out_col.is_empty() { |
| self.out_col = Arc::clone(out_col); |
| } else { |
| self.out_col = concat(&[&self.out_col, &out_col])?; |
| } |
| self.n_row_result_missing = |
| partition_batch_state.record_batch.num_rows() - self.last_calculated_index; |
| self.is_end = partition_batch_state.is_end; |
| Ok(()) |
| } |
| |
| pub fn new(out_type: &DataType) -> Result<Self> { |
| let empty_out_col = ScalarValue::try_from(out_type)?.to_array_of_size(0)?; |
| Ok(Self { |
| window_frame_range: Range { start: 0, end: 0 }, |
| window_frame_ctx: None, |
| last_calculated_index: 0, |
| offset_pruned_rows: 0, |
| out_col: empty_out_col, |
| n_row_result_missing: 0, |
| is_end: false, |
| }) |
| } |
| } |
| |
| /// This object stores the window frame state for use in incremental calculations. |
| #[derive(Debug, Clone)] |
| pub enum WindowFrameContext { |
| /// ROWS frames are inherently stateless. |
| Rows(Arc<WindowFrame>), |
| /// RANGE frames are stateful, they store indices specifying where the |
| /// previous search left off. This amortizes the overall cost to O(n) |
| /// where n denotes the row count. |
| Range { |
| window_frame: Arc<WindowFrame>, |
| state: WindowFrameStateRange, |
| }, |
| /// GROUPS frames are stateful, they store group boundaries and indices |
| /// specifying where the previous search left off. This amortizes the |
| /// overall cost to O(n) where n denotes the row count. |
| Groups { |
| window_frame: Arc<WindowFrame>, |
| state: WindowFrameStateGroups, |
| }, |
| } |
| |
| impl WindowFrameContext { |
| /// Create a new state object for the given window frame. |
| pub fn new(window_frame: Arc<WindowFrame>, sort_options: Vec<SortOptions>) -> Self { |
| match window_frame.units { |
| WindowFrameUnits::Rows => WindowFrameContext::Rows(window_frame), |
| WindowFrameUnits::Range => WindowFrameContext::Range { |
| window_frame, |
| state: WindowFrameStateRange::new(sort_options), |
| }, |
| WindowFrameUnits::Groups => WindowFrameContext::Groups { |
| window_frame, |
| state: WindowFrameStateGroups::default(), |
| }, |
| } |
| } |
| |
| /// This function calculates beginning/ending indices for the frame of the current row. |
| pub fn calculate_range( |
| &mut self, |
| range_columns: &[ArrayRef], |
| last_range: &Range<usize>, |
| length: usize, |
| idx: usize, |
| ) -> Result<Range<usize>> { |
| match self { |
| WindowFrameContext::Rows(window_frame) => { |
| Self::calculate_range_rows(window_frame, length, idx) |
| } |
| // Sort options is used in RANGE mode calculations because the |
| // ordering or position of NULLs impact range calculations and |
| // comparison of rows. |
| WindowFrameContext::Range { |
| window_frame, |
| ref mut state, |
| } => state.calculate_range( |
| window_frame, |
| last_range, |
| range_columns, |
| length, |
| idx, |
| ), |
| // Sort options is not used in GROUPS mode calculations as the |
| // inequality of two rows indicates a group change, and ordering |
| // or position of NULLs do not impact inequality. |
| WindowFrameContext::Groups { |
| window_frame, |
| ref mut state, |
| } => state.calculate_range(window_frame, range_columns, length, idx), |
| } |
| } |
| |
| /// This function calculates beginning/ending indices for the frame of the current row. |
| fn calculate_range_rows( |
| window_frame: &Arc<WindowFrame>, |
| length: usize, |
| idx: usize, |
| ) -> Result<Range<usize>> { |
| let start = match window_frame.start_bound { |
| // UNBOUNDED PRECEDING |
| WindowFrameBound::Preceding(ScalarValue::UInt64(None)) => 0, |
| WindowFrameBound::Preceding(ScalarValue::UInt64(Some(n))) => { |
| idx.saturating_sub(n as usize) |
| } |
| WindowFrameBound::CurrentRow => idx, |
| // UNBOUNDED FOLLOWING |
| WindowFrameBound::Following(ScalarValue::UInt64(None)) => { |
| return internal_err!( |
| "Frame start cannot be UNBOUNDED FOLLOWING '{window_frame:?}'" |
| ) |
| } |
| WindowFrameBound::Following(ScalarValue::UInt64(Some(n))) => { |
| std::cmp::min(idx + n as usize, length) |
| } |
| // ERRONEOUS FRAMES |
| WindowFrameBound::Preceding(_) | WindowFrameBound::Following(_) => { |
| return internal_err!("Rows should be UInt64") |
| } |
| }; |
| let end = match window_frame.end_bound { |
| // UNBOUNDED PRECEDING |
| WindowFrameBound::Preceding(ScalarValue::UInt64(None)) => { |
| return internal_err!( |
| "Frame end cannot be UNBOUNDED PRECEDING '{window_frame:?}'" |
| ) |
| } |
| WindowFrameBound::Preceding(ScalarValue::UInt64(Some(n))) => { |
| if idx >= n as usize { |
| idx - n as usize + 1 |
| } else { |
| 0 |
| } |
| } |
| WindowFrameBound::CurrentRow => idx + 1, |
| // UNBOUNDED FOLLOWING |
| WindowFrameBound::Following(ScalarValue::UInt64(None)) => length, |
| WindowFrameBound::Following(ScalarValue::UInt64(Some(n))) => { |
| std::cmp::min(idx + n as usize + 1, length) |
| } |
| // ERRONEOUS FRAMES |
| WindowFrameBound::Preceding(_) | WindowFrameBound::Following(_) => { |
| return internal_err!("Rows should be UInt64") |
| } |
| }; |
| Ok(Range { start, end }) |
| } |
| } |
| |
| /// State for each unique partition determined according to PARTITION BY column(s) |
| #[derive(Debug, Clone, PartialEq)] |
| pub struct PartitionBatchState { |
| /// The record batch belonging to current partition |
| pub record_batch: RecordBatch, |
| /// The record batch that contains the most recent row at the input. |
| /// Please note that this batch doesn't necessarily have the same partitioning |
| /// with `record_batch`. Keeping track of this batch enables us to prune |
| /// `record_batch` when cardinality of the partition is sparse. |
| pub most_recent_row: Option<RecordBatch>, |
| /// Flag indicating whether we have received all data for this partition |
| pub is_end: bool, |
| /// Number of rows emitted for each partition |
| pub n_out_row: usize, |
| } |
| |
| impl PartitionBatchState { |
| pub fn new(schema: SchemaRef) -> Self { |
| Self { |
| record_batch: RecordBatch::new_empty(schema), |
| most_recent_row: None, |
| is_end: false, |
| n_out_row: 0, |
| } |
| } |
| |
| pub fn new_with_batch(batch: RecordBatch) -> Self { |
| Self { |
| record_batch: batch, |
| most_recent_row: None, |
| is_end: false, |
| n_out_row: 0, |
| } |
| } |
| |
| pub fn extend(&mut self, batch: &RecordBatch) -> Result<()> { |
| self.record_batch = |
| concat_batches(&self.record_batch.schema(), [&self.record_batch, batch])?; |
| Ok(()) |
| } |
| |
| pub fn set_most_recent_row(&mut self, batch: RecordBatch) { |
| // It is enough for the batch to contain only a single row (the rest |
| // are not necessary). |
| self.most_recent_row = Some(batch); |
| } |
| } |
| |
| /// This structure encapsulates all the state information we require as we scan |
| /// ranges of data while processing RANGE frames. |
| /// Attribute `sort_options` stores the column ordering specified by the ORDER |
| /// BY clause. This information is used to calculate the range. |
| #[derive(Debug, Default, Clone)] |
| pub struct WindowFrameStateRange { |
| sort_options: Vec<SortOptions>, |
| } |
| |
| impl WindowFrameStateRange { |
| /// Create a new object to store the search state. |
| fn new(sort_options: Vec<SortOptions>) -> Self { |
| Self { sort_options } |
| } |
| |
| /// This function calculates beginning/ending indices for the frame of the current row. |
| // Argument `last_range` stores the resulting indices from the previous search. Since the indices only |
| // advance forward, we start from `last_range` subsequently. Thus, the overall |
| // time complexity of linear search amortizes to O(n) where n denotes the total |
| // row count. |
| fn calculate_range( |
| &mut self, |
| window_frame: &Arc<WindowFrame>, |
| last_range: &Range<usize>, |
| range_columns: &[ArrayRef], |
| length: usize, |
| idx: usize, |
| ) -> Result<Range<usize>> { |
| let start = match window_frame.start_bound { |
| WindowFrameBound::Preceding(ref n) => { |
| if n.is_null() { |
| // UNBOUNDED PRECEDING |
| 0 |
| } else { |
| self.calculate_index_of_row::<true, true>( |
| range_columns, |
| last_range, |
| idx, |
| Some(n), |
| length, |
| )? |
| } |
| } |
| WindowFrameBound::CurrentRow => self.calculate_index_of_row::<true, true>( |
| range_columns, |
| last_range, |
| idx, |
| None, |
| length, |
| )?, |
| WindowFrameBound::Following(ref n) => self |
| .calculate_index_of_row::<true, false>( |
| range_columns, |
| last_range, |
| idx, |
| Some(n), |
| length, |
| )?, |
| }; |
| let end = match window_frame.end_bound { |
| WindowFrameBound::Preceding(ref n) => self |
| .calculate_index_of_row::<false, true>( |
| range_columns, |
| last_range, |
| idx, |
| Some(n), |
| length, |
| )?, |
| WindowFrameBound::CurrentRow => self.calculate_index_of_row::<false, false>( |
| range_columns, |
| last_range, |
| idx, |
| None, |
| length, |
| )?, |
| WindowFrameBound::Following(ref n) => { |
| if n.is_null() { |
| // UNBOUNDED FOLLOWING |
| length |
| } else { |
| self.calculate_index_of_row::<false, false>( |
| range_columns, |
| last_range, |
| idx, |
| Some(n), |
| length, |
| )? |
| } |
| } |
| }; |
| Ok(Range { start, end }) |
| } |
| |
| /// This function does the heavy lifting when finding range boundaries. It is meant to be |
| /// called twice, in succession, to get window frame start and end indices (with `SIDE` |
| /// supplied as true and false, respectively). |
| fn calculate_index_of_row<const SIDE: bool, const SEARCH_SIDE: bool>( |
| &mut self, |
| range_columns: &[ArrayRef], |
| last_range: &Range<usize>, |
| idx: usize, |
| delta: Option<&ScalarValue>, |
| length: usize, |
| ) -> Result<usize> { |
| let current_row_values = get_row_at_idx(range_columns, idx)?; |
| let end_range = if let Some(delta) = delta { |
| let is_descending: bool = self |
| .sort_options |
| .first() |
| .ok_or_else(|| { |
| internal_datafusion_err!( |
| "Sort options unexpectedly absent in a window frame" |
| ) |
| })? |
| .descending; |
| |
| current_row_values |
| .iter() |
| .map(|value| { |
| if value.is_null() { |
| return Ok(value.clone()); |
| } |
| if SEARCH_SIDE == is_descending { |
| // TODO: Handle positive overflows. |
| value.add(delta) |
| } else if value.is_unsigned() && value < delta { |
| // NOTE: This gets a polymorphic zero without having long coercion code for ScalarValue. |
| // If we decide to implement a "default" construction mechanism for ScalarValue, |
| // change the following statement to use that. |
| value.sub(value) |
| } else { |
| // TODO: Handle negative overflows. |
| value.sub(delta) |
| } |
| }) |
| .collect::<Result<Vec<ScalarValue>>>()? |
| } else { |
| current_row_values |
| }; |
| let search_start = if SIDE { |
| last_range.start |
| } else { |
| last_range.end |
| }; |
| let compare_fn = |current: &[ScalarValue], target: &[ScalarValue]| { |
| let cmp = compare_rows(current, target, &self.sort_options)?; |
| Ok(if SIDE { cmp.is_lt() } else { cmp.is_le() }) |
| }; |
| search_in_slice(range_columns, &end_range, compare_fn, search_start, length) |
| } |
| } |
| |
| // In GROUPS mode, rows with duplicate sorting values are grouped together. |
| // Therefore, there must be an ORDER BY clause in the window definition to use GROUPS mode. |
| // The syntax is as follows: |
| // GROUPS frame_start [ frame_exclusion ] |
| // GROUPS BETWEEN frame_start AND frame_end [ frame_exclusion ] |
| // The optional frame_exclusion specifier is not yet supported. |
| // The frame_start and frame_end parameters allow us to specify which rows the window |
| // frame starts and ends with. They accept the following values: |
| // - UNBOUNDED PRECEDING: Start with the first row of the partition. Possible only in frame_start. |
| // - offset PRECEDING: When used in frame_start, it refers to the first row of the group |
| // that comes "offset" groups before the current group (i.e. the group |
| // containing the current row). When used in frame_end, it refers to the |
| // last row of the group that comes "offset" groups before the current group. |
| // - CURRENT ROW: When used in frame_start, it refers to the first row of the group containing |
| // the current row. When used in frame_end, it refers to the last row of the group |
| // containing the current row. |
| // - offset FOLLOWING: When used in frame_start, it refers to the first row of the group |
| // that comes "offset" groups after the current group (i.e. the group |
| // containing the current row). When used in frame_end, it refers to the |
| // last row of the group that comes "offset" groups after the current group. |
| // - UNBOUNDED FOLLOWING: End with the last row of the partition. Possible only in frame_end. |
| |
| /// This structure encapsulates all the state information we require as we |
| /// scan groups of data while processing window frames. |
| #[derive(Debug, Default, Clone)] |
| pub struct WindowFrameStateGroups { |
| /// A tuple containing group values and the row index where the group ends. |
| /// Example: [[1, 1], [1, 1], [2, 1], [2, 1], ...] would correspond to |
| /// [([1, 1], 2), ([2, 1], 4), ...]. |
| pub group_end_indices: VecDeque<(Vec<ScalarValue>, usize)>, |
| /// The group index to which the row index belongs. |
| pub current_group_idx: usize, |
| } |
| |
| impl WindowFrameStateGroups { |
| fn calculate_range( |
| &mut self, |
| window_frame: &Arc<WindowFrame>, |
| range_columns: &[ArrayRef], |
| length: usize, |
| idx: usize, |
| ) -> Result<Range<usize>> { |
| let start = match window_frame.start_bound { |
| WindowFrameBound::Preceding(ref n) => { |
| if n.is_null() { |
| // UNBOUNDED PRECEDING |
| 0 |
| } else { |
| self.calculate_index_of_row::<true, true>( |
| range_columns, |
| idx, |
| Some(n), |
| length, |
| )? |
| } |
| } |
| WindowFrameBound::CurrentRow => self.calculate_index_of_row::<true, true>( |
| range_columns, |
| idx, |
| None, |
| length, |
| )?, |
| WindowFrameBound::Following(ref n) => self |
| .calculate_index_of_row::<true, false>( |
| range_columns, |
| idx, |
| Some(n), |
| length, |
| )?, |
| }; |
| let end = match window_frame.end_bound { |
| WindowFrameBound::Preceding(ref n) => self |
| .calculate_index_of_row::<false, true>( |
| range_columns, |
| idx, |
| Some(n), |
| length, |
| )?, |
| WindowFrameBound::CurrentRow => self.calculate_index_of_row::<false, false>( |
| range_columns, |
| idx, |
| None, |
| length, |
| )?, |
| WindowFrameBound::Following(ref n) => { |
| if n.is_null() { |
| // UNBOUNDED FOLLOWING |
| length |
| } else { |
| self.calculate_index_of_row::<false, false>( |
| range_columns, |
| idx, |
| Some(n), |
| length, |
| )? |
| } |
| } |
| }; |
| Ok(Range { start, end }) |
| } |
| |
| /// This function does the heavy lifting when finding range boundaries. It is meant to be |
| /// called twice, in succession, to get window frame start and end indices (with `SIDE` |
| /// supplied as true and false, respectively). Generic argument `SEARCH_SIDE` determines |
| /// the sign of `delta` (where true/false represents negative/positive respectively). |
| fn calculate_index_of_row<const SIDE: bool, const SEARCH_SIDE: bool>( |
| &mut self, |
| range_columns: &[ArrayRef], |
| idx: usize, |
| delta: Option<&ScalarValue>, |
| length: usize, |
| ) -> Result<usize> { |
| let delta = if let Some(delta) = delta { |
| if let ScalarValue::UInt64(Some(value)) = delta { |
| *value as usize |
| } else { |
| return internal_err!( |
| "Unexpectedly got a non-UInt64 value in a GROUPS mode window frame" |
| ); |
| } |
| } else { |
| 0 |
| }; |
| let mut group_start = 0; |
| let last_group = self.group_end_indices.back_mut(); |
| if let Some((group_row, group_end)) = last_group { |
| if *group_end < length { |
| let new_group_row = get_row_at_idx(range_columns, *group_end)?; |
| // If last/current group keys are the same, we extend the last group: |
| if new_group_row.eq(group_row) { |
| // Update the end boundary of the group (search right boundary): |
| *group_end = search_in_slice( |
| range_columns, |
| group_row, |
| check_equality, |
| *group_end, |
| length, |
| )?; |
| } |
| } |
| // Start searching from the last group boundary: |
| group_start = *group_end; |
| } |
| |
| // Advance groups until `idx` is inside a group: |
| while idx >= group_start { |
| let group_row = get_row_at_idx(range_columns, group_start)?; |
| // Find end boundary of the group (search right boundary): |
| let group_end = search_in_slice( |
| range_columns, |
| &group_row, |
| check_equality, |
| group_start, |
| length, |
| )?; |
| self.group_end_indices.push_back((group_row, group_end)); |
| group_start = group_end; |
| } |
| |
| // Update the group index `idx` belongs to: |
| while self.current_group_idx < self.group_end_indices.len() |
| && idx >= self.group_end_indices[self.current_group_idx].1 |
| { |
| self.current_group_idx += 1; |
| } |
| |
| // Find the group index of the frame boundary: |
| let group_idx = if SEARCH_SIDE { |
| self.current_group_idx.saturating_sub(delta) |
| } else { |
| self.current_group_idx + delta |
| }; |
| |
| // Extend `group_start_indices` until it includes at least `group_idx`: |
| while self.group_end_indices.len() <= group_idx && group_start < length { |
| let group_row = get_row_at_idx(range_columns, group_start)?; |
| // Find end boundary of the group (search right boundary): |
| let group_end = search_in_slice( |
| range_columns, |
| &group_row, |
| check_equality, |
| group_start, |
| length, |
| )?; |
| self.group_end_indices.push_back((group_row, group_end)); |
| group_start = group_end; |
| } |
| |
| // Calculate index of the group boundary: |
| Ok(match (SIDE, SEARCH_SIDE) { |
| // Window frame start: |
| (true, _) => { |
| let group_idx = std::cmp::min(group_idx, self.group_end_indices.len()); |
| if group_idx > 0 { |
| // Normally, start at the boundary of the previous group. |
| self.group_end_indices[group_idx - 1].1 |
| } else { |
| // If previous group is out of the table, start at zero. |
| 0 |
| } |
| } |
| // Window frame end, PRECEDING n |
| (false, true) => { |
| if self.current_group_idx >= delta { |
| let group_idx = self.current_group_idx - delta; |
| self.group_end_indices[group_idx].1 |
| } else { |
| // Group is out of the table, therefore end at zero. |
| 0 |
| } |
| } |
| // Window frame end, FOLLOWING n |
| (false, false) => { |
| let group_idx = std::cmp::min( |
| self.current_group_idx + delta, |
| self.group_end_indices.len() - 1, |
| ); |
| self.group_end_indices[group_idx].1 |
| } |
| }) |
| } |
| } |
| |
| fn check_equality(current: &[ScalarValue], target: &[ScalarValue]) -> Result<bool> { |
| Ok(current == target) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| use arrow::array::Float64Array; |
| |
| fn get_test_data() -> (Vec<ArrayRef>, Vec<SortOptions>) { |
| let range_columns: Vec<ArrayRef> = vec![Arc::new(Float64Array::from(vec![ |
| 5.0, 7.0, 8.0, 8.0, 9., 10., 10., 10., 11., |
| ]))]; |
| let sort_options = vec![SortOptions { |
| descending: false, |
| nulls_first: false, |
| }]; |
| |
| (range_columns, sort_options) |
| } |
| |
| fn assert_group_ranges( |
| window_frame: &Arc<WindowFrame>, |
| expected_results: Vec<(Range<usize>, usize)>, |
| ) -> Result<()> { |
| let mut window_frame_groups = WindowFrameStateGroups::default(); |
| let (range_columns, _) = get_test_data(); |
| let n_row = range_columns[0].len(); |
| for (idx, (expected_range, expected_group_idx)) in |
| expected_results.into_iter().enumerate() |
| { |
| let range = window_frame_groups.calculate_range( |
| window_frame, |
| &range_columns, |
| n_row, |
| idx, |
| )?; |
| assert_eq!(range, expected_range); |
| assert_eq!(window_frame_groups.current_group_idx, expected_group_idx); |
| } |
| Ok(()) |
| } |
| |
| fn assert_frame_ranges( |
| window_frame: &Arc<WindowFrame>, |
| expected_results: Vec<Range<usize>>, |
| ) -> Result<()> { |
| let mut window_frame_context = |
| WindowFrameContext::new(Arc::clone(window_frame), vec![]); |
| let (range_columns, _) = get_test_data(); |
| let n_row = range_columns[0].len(); |
| let mut last_range = Range { start: 0, end: 0 }; |
| for (idx, expected_range) in expected_results.into_iter().enumerate() { |
| let range = window_frame_context.calculate_range( |
| &range_columns, |
| &last_range, |
| n_row, |
| idx, |
| )?; |
| assert_eq!(range, expected_range); |
| last_range = range; |
| } |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_default_window_frame_group_boundaries() -> Result<()> { |
| let window_frame = Arc::new(WindowFrame::new(None)); |
| assert_group_ranges( |
| &window_frame, |
| vec![ |
| (Range { start: 0, end: 9 }, 0), |
| (Range { start: 0, end: 9 }, 0), |
| (Range { start: 0, end: 9 }, 0), |
| (Range { start: 0, end: 9 }, 0), |
| (Range { start: 0, end: 9 }, 0), |
| (Range { start: 0, end: 9 }, 0), |
| (Range { start: 0, end: 9 }, 0), |
| (Range { start: 0, end: 9 }, 0), |
| (Range { start: 0, end: 9 }, 0), |
| ], |
| )?; |
| |
| assert_frame_ranges( |
| &window_frame, |
| vec![ |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| ], |
| )?; |
| |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_unordered_window_frame_group_boundaries() -> Result<()> { |
| let window_frame = Arc::new(WindowFrame::new(Some(false))); |
| assert_group_ranges( |
| &window_frame, |
| vec![ |
| (Range { start: 0, end: 1 }, 0), |
| (Range { start: 0, end: 2 }, 1), |
| (Range { start: 0, end: 4 }, 2), |
| (Range { start: 0, end: 4 }, 2), |
| (Range { start: 0, end: 5 }, 3), |
| (Range { start: 0, end: 8 }, 4), |
| (Range { start: 0, end: 8 }, 4), |
| (Range { start: 0, end: 8 }, 4), |
| (Range { start: 0, end: 9 }, 5), |
| ], |
| )?; |
| |
| assert_frame_ranges( |
| &window_frame, |
| vec![ |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| Range { start: 0, end: 9 }, |
| ], |
| )?; |
| |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_ordered_window_frame_group_boundaries() -> Result<()> { |
| let window_frame = Arc::new(WindowFrame::new(Some(true))); |
| assert_group_ranges( |
| &window_frame, |
| vec![ |
| (Range { start: 0, end: 1 }, 0), |
| (Range { start: 0, end: 2 }, 1), |
| (Range { start: 0, end: 4 }, 2), |
| (Range { start: 0, end: 4 }, 2), |
| (Range { start: 0, end: 5 }, 3), |
| (Range { start: 0, end: 8 }, 4), |
| (Range { start: 0, end: 8 }, 4), |
| (Range { start: 0, end: 8 }, 4), |
| (Range { start: 0, end: 9 }, 5), |
| ], |
| )?; |
| |
| assert_frame_ranges( |
| &window_frame, |
| vec![ |
| Range { start: 0, end: 1 }, |
| Range { start: 0, end: 2 }, |
| Range { start: 0, end: 3 }, |
| Range { start: 0, end: 4 }, |
| Range { start: 0, end: 5 }, |
| Range { start: 0, end: 6 }, |
| Range { start: 0, end: 7 }, |
| Range { start: 0, end: 8 }, |
| Range { start: 0, end: 9 }, |
| ], |
| )?; |
| |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_window_frame_group_boundaries() -> Result<()> { |
| let window_frame = Arc::new(WindowFrame::new_bounds( |
| WindowFrameUnits::Groups, |
| WindowFrameBound::Preceding(ScalarValue::UInt64(Some(1))), |
| WindowFrameBound::Following(ScalarValue::UInt64(Some(1))), |
| )); |
| assert_group_ranges( |
| &window_frame, |
| vec![ |
| (Range { start: 0, end: 2 }, 0), |
| (Range { start: 0, end: 4 }, 1), |
| (Range { start: 1, end: 5 }, 2), |
| (Range { start: 1, end: 5 }, 2), |
| (Range { start: 2, end: 8 }, 3), |
| (Range { start: 4, end: 9 }, 4), |
| (Range { start: 4, end: 9 }, 4), |
| (Range { start: 4, end: 9 }, 4), |
| (Range { start: 5, end: 9 }, 5), |
| ], |
| ) |
| } |
| |
| #[test] |
| fn test_window_frame_group_boundaries_both_following() -> Result<()> { |
| let window_frame = Arc::new(WindowFrame::new_bounds( |
| WindowFrameUnits::Groups, |
| WindowFrameBound::Following(ScalarValue::UInt64(Some(1))), |
| WindowFrameBound::Following(ScalarValue::UInt64(Some(2))), |
| )); |
| assert_group_ranges( |
| &window_frame, |
| vec![ |
| (Range::<usize> { start: 1, end: 4 }, 0), |
| (Range::<usize> { start: 2, end: 5 }, 1), |
| (Range::<usize> { start: 4, end: 8 }, 2), |
| (Range::<usize> { start: 4, end: 8 }, 2), |
| (Range::<usize> { start: 5, end: 9 }, 3), |
| (Range::<usize> { start: 8, end: 9 }, 4), |
| (Range::<usize> { start: 8, end: 9 }, 4), |
| (Range::<usize> { start: 8, end: 9 }, 4), |
| (Range::<usize> { start: 9, end: 9 }, 5), |
| ], |
| ) |
| } |
| |
| #[test] |
| fn test_window_frame_group_boundaries_both_preceding() -> Result<()> { |
| let window_frame = Arc::new(WindowFrame::new_bounds( |
| WindowFrameUnits::Groups, |
| WindowFrameBound::Preceding(ScalarValue::UInt64(Some(2))), |
| WindowFrameBound::Preceding(ScalarValue::UInt64(Some(1))), |
| )); |
| assert_group_ranges( |
| &window_frame, |
| vec![ |
| (Range::<usize> { start: 0, end: 0 }, 0), |
| (Range::<usize> { start: 0, end: 1 }, 1), |
| (Range::<usize> { start: 0, end: 2 }, 2), |
| (Range::<usize> { start: 0, end: 2 }, 2), |
| (Range::<usize> { start: 1, end: 4 }, 3), |
| (Range::<usize> { start: 2, end: 5 }, 4), |
| (Range::<usize> { start: 2, end: 5 }, 4), |
| (Range::<usize> { start: 2, end: 5 }, 4), |
| (Range::<usize> { start: 4, end: 8 }, 5), |
| ], |
| ) |
| } |
| } |