Skip to main content

radixdb_executor/window/
mod.rs

1// Copyright 2026 RadixDB Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Window Function Execution
16//!
17//! This module implements window function execution for SQL queries.
18//!
19//! Supports:
20//! - ROW_NUMBER() - Sequential row numbering
21//! - RANK() - Ranking with gaps
22//! - DENSE_RANK() - Ranking without gaps
23//! - NTILE(n) - Divides rows into n groups
24//! - LEAD(col, offset, default) - Access next row's value
25//! - LAG(col, offset, default) - Access previous row's value
26//!
27//! Window clauses:
28//! - OVER () - Entire result set as one partition
29//! - OVER (PARTITION BY col) - Partition by column values
30//! - OVER (ORDER BY col) - Order within partition
31
32#[cfg(feature = "parallel")]
33use rayon::prelude::*;
34use rustc_hash::FxHashMap;
35use smallvec::SmallVec;
36use std::cmp::Ordering;
37
38use radixdb_core::row_vec::RowVec;
39use radixdb_core::value::NULL_VALUE;
40use radixdb_core::{CompactVec, StringMap};
41use radixdb_core::{Error, Result, Row, Value};
42
43/// Type alias for partition keys - stack-allocated for common case (up to 4 columns)
44type PartitionKey = SmallVec<[Value; 4]>;
45
46use radixdb_functions::{FunctionRegistry, WindowFunction};
47use radixdb_sql::ast::*;
48use radixdb_storage::traits::{QueryResult, Table};
49
50use super::context::ExecutionContext;
51use super::expression::{ExpressionEval, MultiExpressionEval};
52use super::result::{ColumnarResult, ExecutorResult};
53use super::utils::build_column_index_map;
54
55mod aggregate;
56mod execute;
57mod partition;
58mod planning;
59#[cfg(test)]
60mod tests;
61
62/// Narrow composition contract required by window planning and execution.
63pub trait WindowHost: Sync {
64    fn window_function_registry(&self) -> &FunctionRegistry;
65}
66
67/// Single owner for window planning, partition state, execution and result
68/// finalization. The host supplies only the immutable function registry.
69pub struct WindowExecutor<'a, H: WindowHost + ?Sized> {
70    host: &'a H,
71}
72
73impl<'a, H: WindowHost + ?Sized> WindowExecutor<'a, H> {
74    fn new(host: &'a H) -> Self {
75        Self { host }
76    }
77}
78
79/// Compatibility surface used by the composition root while the surrounding
80/// SELECT orchestration is migrated.
81pub trait WindowExecutorExt: WindowHost {
82    fn execute_select_with_window_functions(
83        &self,
84        stmt: &SelectStatement,
85        ctx: &ExecutionContext,
86        base_rows: &[(i64, Row)],
87        base_columns: &[String],
88    ) -> Result<Box<dyn QueryResult>> {
89        WindowExecutor::new(self).execute_select_with_window_functions(
90            stmt,
91            ctx,
92            base_rows,
93            base_columns,
94        )
95    }
96
97    fn execute_select_with_window_functions_presorted(
98        &self,
99        stmt: &SelectStatement,
100        ctx: &ExecutionContext,
101        base_rows: &[(i64, Row)],
102        base_columns: &[String],
103        pre_sorted: Option<WindowPreSortedState>,
104    ) -> Result<Box<dyn QueryResult>> {
105        WindowExecutor::new(self).execute_select_with_window_functions_presorted(
106            stmt,
107            ctx,
108            base_rows,
109            base_columns,
110            pre_sorted,
111        )
112    }
113
114    fn execute_select_with_window_functions_pregrouped(
115        &self,
116        stmt: &SelectStatement,
117        ctx: &ExecutionContext,
118        base_rows: &[(i64, Row)],
119        base_columns: &[String],
120        pre_grouped: WindowPreGroupedState,
121    ) -> Result<Box<dyn QueryResult>> {
122        WindowExecutor::new(self).execute_select_with_window_functions_pregrouped(
123            stmt,
124            ctx,
125            base_rows,
126            base_columns,
127            pre_grouped,
128        )
129    }
130
131    fn execute_select_with_window_functions_lazy_partition(
132        &self,
133        stmt: &SelectStatement,
134        ctx: &ExecutionContext,
135        table: &dyn Table,
136        base_columns: &[String],
137        partition_col: &str,
138        limit: usize,
139    ) -> Result<Box<dyn QueryResult>> {
140        WindowExecutor::new(self).execute_select_with_window_functions_lazy_partition(
141            stmt,
142            ctx,
143            table,
144            base_columns,
145            partition_col,
146            limit,
147        )
148    }
149}
150
151impl<T: WindowHost + ?Sized> WindowExecutorExt for T {}
152
153/// Information about a window function call in a SELECT list
154#[derive(Clone, Debug)]
155pub struct WindowFunctionInfo {
156    /// The window function name (ROW_NUMBER, RANK, etc.)
157    pub name: String,
158    /// Arguments to the function (for LEAD, LAG, NTILE)
159    pub arguments: Vec<Expression>,
160    /// Partition by column names (simple identifiers only, for fast-path lookups)
161    pub partition_by: Vec<String>,
162    /// Original PARTITION BY expressions (includes function calls, complex exprs)
163    pub partition_by_exprs: Vec<Expression>,
164    /// Order by expressions
165    pub order_by: Vec<OrderByExpression>,
166    /// Window frame specification (e.g., ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
167    pub frame: Option<WindowFrame>,
168    /// Result column name (may include alias)
169    pub column_name: String,
170    /// Whether DISTINCT was specified (for COUNT(DISTINCT col) OVER())
171    pub is_distinct: bool,
172}
173
174/// Information about a SELECT list item for window function processing
175pub struct SelectItem {
176    pub output_name: String,
177    pub source: SelectItemSource,
178}
179
180/// Source of a SELECT item value
181#[allow(clippy::large_enum_variant)]
182pub enum SelectItemSource {
183    BaseColumn(usize),
184    /// Window function name - stored in lowercase for O(1) lookup in window_value_map
185    WindowFunction(String),
186    Expression(Expression),
187    /// Expression containing one or more window functions.
188    /// Stores (expression, list_of_window_function_names_lowercase).
189    /// Each Window node in the expression is replaced with a placeholder identifier
190    /// referencing the corresponding name at the same index.
191    ExpressionWithWindow(Expression, Vec<String>),
192}
193
194/// Pre-sorted state for window function optimization
195/// When rows are pre-sorted by an indexed column, we can skip sorting in window functions
196#[derive(Clone, Debug)]
197pub struct WindowPreSortedState {
198    /// Column name that rows are sorted by (lowercase)
199    pub column: String,
200    /// Whether sorted in ascending order
201    pub ascending: bool,
202}
203
204/// Columnar layout for ORDER BY values - optimized for sorting performance
205///
206/// Instead of `Vec<Vec<(Value, bool)>>` (row-oriented, N allocations for N rows),
207/// this uses `Vec<Vec<Value>>` (column-oriented, K allocations for K ORDER BY columns).
208///
209/// Benefits:
210/// - Reduces allocations from O(N) to O(K) where K = number of ORDER BY columns
211/// - Stores ascending flags once per column instead of once per value
212/// - Better cache locality when accessing sort keys across rows
213#[derive(Clone, Debug)]
214pub struct ColumnarOrderByValues {
215    /// Column values: columns[col_idx][row_idx] = value
216    columns: Vec<Vec<Value>>,
217    /// Ascending flags: one per ORDER BY column
218    ascending: Vec<bool>,
219    /// Resolved NULL placement: one per ORDER BY column.
220    nulls_first: Vec<bool>,
221    /// Number of rows
222    num_rows: usize,
223}
224
225impl ColumnarOrderByValues {
226    /// Check if empty
227    #[inline]
228    pub fn is_empty(&self) -> bool {
229        self.num_rows == 0 || self.columns.is_empty()
230    }
231
232    /// Get number of columns
233    #[inline]
234    pub fn num_columns(&self) -> usize {
235        self.columns.len()
236    }
237
238    /// Get value at (row, column)
239    #[inline]
240    pub fn get(&self, row_idx: usize, col_idx: usize) -> Option<&Value> {
241        self.columns.get(col_idx).and_then(|col| col.get(row_idx))
242    }
243
244    /// Get first ORDER BY value for a row
245    #[inline]
246    pub fn get_first(&self, row_idx: usize) -> Option<&Value> {
247        self.get(row_idx, 0)
248    }
249
250    /// Get ascending flag for column
251    #[inline]
252    pub fn is_ascending(&self, col_idx: usize) -> bool {
253        self.ascending.get(col_idx).copied().unwrap_or(true)
254    }
255
256    /// Get resolved NULL placement for column.
257    #[inline]
258    pub fn nulls_first(&self, col_idx: usize) -> bool {
259        self.nulls_first.get(col_idx).copied().unwrap_or(false)
260    }
261
262    /// Compare ORDER BY values of two rows for equality (without cloning)
263    /// Returns true if all ORDER BY column values are equal
264    #[inline]
265    pub fn rows_equal(&self, row_a: usize, row_b: usize) -> bool {
266        for col in &self.columns {
267            let val_a = col.get(row_a);
268            let val_b = col.get(row_b);
269            match (val_a, val_b) {
270                (Some(a), Some(b)) if a == b => continue,
271                (None, None) => continue,
272                _ => return false,
273            }
274        }
275        true
276    }
277}
278
279/// Pre-grouped state for window function PARTITION BY optimization
280/// When rows are fetched grouped by an indexed partition column, we can skip hash-based grouping
281#[derive(Clone)]
282pub struct WindowPreGroupedState {
283    /// Pre-built partition map: partition key -> row indices
284    pub partition_map: FxHashMap<PartitionKey, Vec<usize>>,
285    /// The column name (lowercase) that the partition map was built from
286    pub partition_column: String,
287}