Skip to main content

featherdb_query/expr/
window.rs

1//! Window function types and frame specifications
2
3use super::Expr;
4
5/// Window function types
6#[derive(Debug, Clone, PartialEq)]
7pub enum WindowFunctionType {
8    /// ROW_NUMBER() - assigns sequential row numbers
9    RowNumber,
10    /// RANK() - assigns rank with gaps for ties
11    Rank,
12    /// DENSE_RANK() - assigns rank without gaps
13    DenseRank,
14    /// NTILE(n) - divides rows into n buckets
15    NTile(u32),
16    /// LAG(expr, offset, default) - access previous row
17    Lag {
18        expr: Box<Expr>,
19        offset: i64,
20        default: Option<Box<Expr>>,
21    },
22    /// LEAD(expr, offset, default) - access following row
23    Lead {
24        expr: Box<Expr>,
25        offset: i64,
26        default: Option<Box<Expr>>,
27    },
28    /// FIRST_VALUE(expr) - first value in frame
29    FirstValue(Box<Expr>),
30    /// LAST_VALUE(expr) - last value in frame
31    LastValue(Box<Expr>),
32    /// NTH_VALUE(expr, n) - nth value in frame
33    NthValue(Box<Expr>, u32),
34    /// Aggregate function over window
35    Sum(Box<Expr>),
36    Avg(Box<Expr>),
37    Count(Option<Box<Expr>>),
38    Min(Box<Expr>),
39    Max(Box<Expr>),
40}
41
42/// Window frame unit
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum FrameUnit {
45    /// ROWS - physical rows
46    Rows,
47    /// RANGE - logical range based on ORDER BY values
48    Range,
49    /// GROUPS - groups of peer rows
50    Groups,
51}
52
53/// Window frame bound specification
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum FrameBound {
56    /// UNBOUNDED PRECEDING
57    UnboundedPreceding,
58    /// n PRECEDING
59    Preceding(u64),
60    /// CURRENT ROW
61    CurrentRow,
62    /// n FOLLOWING
63    Following(u64),
64    /// UNBOUNDED FOLLOWING
65    UnboundedFollowing,
66}
67
68/// Window frame specification
69#[derive(Debug, Clone, PartialEq)]
70pub struct WindowFrame {
71    /// Frame unit (ROWS, RANGE, or GROUPS)
72    pub unit: FrameUnit,
73    /// Start bound of the frame
74    pub start: FrameBound,
75    /// End bound of the frame (defaults to CURRENT ROW if not specified)
76    pub end: FrameBound,
77}
78
79impl Default for WindowFrame {
80    fn default() -> Self {
81        // Default frame is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
82        WindowFrame {
83            unit: FrameUnit::Range,
84            start: FrameBound::UnboundedPreceding,
85            end: FrameBound::CurrentRow,
86        }
87    }
88}
89
90impl WindowFrame {
91    /// Create a frame for the entire partition (unbounded)
92    pub fn unbounded() -> Self {
93        WindowFrame {
94            unit: FrameUnit::Rows,
95            start: FrameBound::UnboundedPreceding,
96            end: FrameBound::UnboundedFollowing,
97        }
98    }
99
100    /// Create a frame from start to current row
101    pub fn to_current_row() -> Self {
102        WindowFrame {
103            unit: FrameUnit::Rows,
104            start: FrameBound::UnboundedPreceding,
105            end: FrameBound::CurrentRow,
106        }
107    }
108}
109
110/// Sort order for ORDER BY in window functions
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum WindowSortOrder {
113    Asc,
114    Desc,
115}
116
117/// ORDER BY expression for window functions
118#[derive(Debug, Clone, PartialEq)]
119pub struct WindowOrderByExpr {
120    pub expr: Expr,
121    pub order: WindowSortOrder,
122    pub nulls_first: Option<bool>,
123}
124
125/// Complete window function specification
126#[derive(Debug, Clone, PartialEq)]
127pub struct WindowFunction {
128    /// The window function to apply
129    pub function: WindowFunctionType,
130    /// PARTITION BY expressions (optional)
131    pub partition_by: Vec<Expr>,
132    /// ORDER BY expressions (optional but required for some functions)
133    pub order_by: Vec<WindowOrderByExpr>,
134    /// Window frame specification (optional)
135    pub frame: Option<WindowFrame>,
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_window_frame_default() {
144        let frame = WindowFrame::default();
145        assert_eq!(frame.unit, FrameUnit::Range);
146        assert_eq!(frame.start, FrameBound::UnboundedPreceding);
147        assert_eq!(frame.end, FrameBound::CurrentRow);
148    }
149
150    #[test]
151    fn test_window_frame_unbounded() {
152        let frame = WindowFrame::unbounded();
153        assert_eq!(frame.unit, FrameUnit::Rows);
154        assert_eq!(frame.start, FrameBound::UnboundedPreceding);
155        assert_eq!(frame.end, FrameBound::UnboundedFollowing);
156    }
157
158    #[test]
159    fn test_window_frame_to_current_row() {
160        let frame = WindowFrame::to_current_row();
161        assert_eq!(frame.unit, FrameUnit::Rows);
162        assert_eq!(frame.start, FrameBound::UnboundedPreceding);
163        assert_eq!(frame.end, FrameBound::CurrentRow);
164    }
165
166    #[test]
167    fn test_window_function_type_clone() {
168        let func = WindowFunctionType::RowNumber;
169        let cloned = func.clone();
170        assert_eq!(func, cloned);
171    }
172
173    #[test]
174    fn test_window_frame_clone() {
175        let frame = WindowFrame {
176            unit: FrameUnit::Rows,
177            start: FrameBound::Preceding(5),
178            end: FrameBound::Following(5),
179        };
180        let cloned = frame.clone();
181        assert_eq!(frame, cloned);
182    }
183
184    #[test]
185    fn test_window_order_by_expr() {
186        let order = WindowOrderByExpr {
187            expr: Expr::Column {
188                table: None,
189                name: "age".to_string(),
190                index: None,
191            },
192            order: WindowSortOrder::Desc,
193            nulls_first: None,
194        };
195        assert_eq!(order.order, WindowSortOrder::Desc);
196        assert_eq!(order.nulls_first, None);
197    }
198
199    #[test]
200    fn test_window_function_complete() {
201        let func = WindowFunction {
202            function: WindowFunctionType::Rank,
203            partition_by: vec![Expr::Column {
204                table: None,
205                name: "department".to_string(),
206                index: None,
207            }],
208            order_by: vec![WindowOrderByExpr {
209                expr: Expr::Column {
210                    table: None,
211                    name: "salary".to_string(),
212                    index: None,
213                },
214                order: WindowSortOrder::Desc,
215                nulls_first: None,
216            }],
217            frame: None,
218        };
219
220        assert!(matches!(func.function, WindowFunctionType::Rank));
221        assert_eq!(func.partition_by.len(), 1);
222        assert_eq!(func.order_by.len(), 1);
223        assert!(func.frame.is_none());
224    }
225
226    #[test]
227    fn test_frame_unit_variants() {
228        let rows = FrameUnit::Rows;
229        let range = FrameUnit::Range;
230        let groups = FrameUnit::Groups;
231
232        assert_ne!(rows, range);
233        assert_ne!(range, groups);
234        assert_ne!(rows, groups);
235    }
236
237    #[test]
238    fn test_frame_bound_variants() {
239        let unbounded_preceding = FrameBound::UnboundedPreceding;
240        let preceding = FrameBound::Preceding(5);
241        let current_row = FrameBound::CurrentRow;
242        let following = FrameBound::Following(5);
243        let unbounded_following = FrameBound::UnboundedFollowing;
244
245        assert_ne!(unbounded_preceding, current_row);
246        assert_ne!(preceding, following);
247        assert_ne!(current_row, unbounded_following);
248    }
249}