1use crate::expression::{Expression, SortOrder};
7
8#[derive(Debug, Clone, PartialEq)]
10pub enum FrameBound {
11 UnboundedPreceding,
13 Preceding(i64),
15 CurrentRow,
17 Following(i64),
19 UnboundedFollowing,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum FrameType {
26 Row,
28 Range,
30}
31
32#[derive(Debug, Clone, PartialEq)]
37pub struct WindowSpec {
38 pub partition_spec: Vec<Expression>,
40 pub order_spec: Vec<SortOrder>,
42 pub frame_spec: Option<(FrameType, FrameBound, FrameBound)>,
44}
45
46impl WindowSpec {
47 pub fn new() -> Self {
49 Self {
50 partition_spec: Vec::new(),
51 order_spec: Vec::new(),
52 frame_spec: None,
53 }
54 }
55
56 pub fn partition_by(mut self, cols: Vec<Expression>) -> Self {
58 self.partition_spec.extend(cols);
59 self
60 }
61
62 pub fn order_by(mut self, sorts: Vec<SortOrder>) -> Self {
64 self.order_spec.extend(sorts);
65 self
66 }
67
68 pub fn rows_between(mut self, start: FrameBound, end: FrameBound) -> Self {
70 self.frame_spec = Some((FrameType::Row, start, end));
71 self
72 }
73
74 pub fn range_between(mut self, start: FrameBound, end: FrameBound) -> Self {
76 self.frame_spec = Some((FrameType::Range, start, end));
77 self
78 }
79}
80
81impl Default for WindowSpec {
82 fn default() -> Self {
83 Self::new()
84 }
85}
86
87pub struct Window;
91
92impl Window {
93 pub const fn unbounded_preceding() -> FrameBound {
95 FrameBound::UnboundedPreceding
96 }
97
98 pub const fn current_row() -> FrameBound {
100 FrameBound::CurrentRow
101 }
102
103 pub const fn unbounded_following() -> FrameBound {
105 FrameBound::UnboundedFollowing
106 }
107
108 pub fn partition_by(cols: Vec<Expression>) -> WindowSpec {
110 WindowSpec::new().partition_by(cols)
111 }
112
113 pub fn order_by(sorts: Vec<SortOrder>) -> WindowSpec {
115 WindowSpec::new().order_by(sorts)
116 }
117
118 pub fn rows_between(start: FrameBound, end: FrameBound) -> WindowSpec {
120 WindowSpec::new().rows_between(start, end)
121 }
122
123 pub fn range_between(start: FrameBound, end: FrameBound) -> WindowSpec {
125 WindowSpec::new().range_between(start, end)
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::expression::{ColumnReference, NullOrdering, SortOrder};
133
134 fn col(name: &str) -> Expression {
135 Expression::ColumnReference(ColumnReference::new(name))
136 }
137
138 fn sort(name: &str) -> SortOrder {
139 SortOrder {
140 child: col(name),
141 ascending: true,
142 null_ordering: NullOrdering::First,
143 }
144 }
145
146 #[test]
147 fn new_and_default_are_empty() {
148 let w = WindowSpec::new();
149 assert!(w.partition_spec.is_empty());
150 assert!(w.order_spec.is_empty());
151 assert!(w.frame_spec.is_none());
152 assert_eq!(WindowSpec::default(), w);
153 }
154
155 #[test]
156 fn spec_builders_chain() {
157 let w = WindowSpec::new()
158 .partition_by(vec![col("a"), col("b")])
159 .order_by(vec![sort("c")]);
160 assert_eq!(w.partition_spec.len(), 2);
161 assert_eq!(w.order_spec.len(), 1);
162 }
163
164 #[test]
165 fn rows_between_sets_row_frame() {
166 let w = WindowSpec::new().rows_between(FrameBound::Preceding(1), FrameBound::Following(2));
167 assert_eq!(
168 w.frame_spec,
169 Some((
170 FrameType::Row,
171 FrameBound::Preceding(1),
172 FrameBound::Following(2)
173 ))
174 );
175 }
176
177 #[test]
178 fn range_between_sets_range_frame() {
179 let w =
180 WindowSpec::new().range_between(FrameBound::UnboundedPreceding, FrameBound::CurrentRow);
181 assert_eq!(
182 w.frame_spec,
183 Some((
184 FrameType::Range,
185 FrameBound::UnboundedPreceding,
186 FrameBound::CurrentRow
187 ))
188 );
189 }
190
191 #[test]
192 fn a_later_frame_replaces_an_earlier_one() {
193 let w = WindowSpec::new()
194 .rows_between(FrameBound::UnboundedPreceding, FrameBound::CurrentRow)
195 .range_between(FrameBound::CurrentRow, FrameBound::UnboundedFollowing);
196 assert_eq!(
197 w.frame_spec,
198 Some((
199 FrameType::Range,
200 FrameBound::CurrentRow,
201 FrameBound::UnboundedFollowing
202 ))
203 );
204 }
205
206 #[test]
207 fn window_static_constructors_match_spec_builders() {
208 assert_eq!(
209 Window::partition_by(vec![col("a")]),
210 WindowSpec::new().partition_by(vec![col("a")])
211 );
212 assert_eq!(
213 Window::order_by(vec![sort("a")]),
214 WindowSpec::new().order_by(vec![sort("a")])
215 );
216 assert_eq!(
217 Window::rows_between(FrameBound::Preceding(1), FrameBound::CurrentRow),
218 WindowSpec::new().rows_between(FrameBound::Preceding(1), FrameBound::CurrentRow)
219 );
220 assert_eq!(
221 Window::range_between(FrameBound::CurrentRow, FrameBound::Following(3)),
222 WindowSpec::new().range_between(FrameBound::CurrentRow, FrameBound::Following(3))
223 );
224 }
225
226 #[test]
227 fn frame_bound_constants() {
228 assert_eq!(
229 Window::unbounded_preceding(),
230 FrameBound::UnboundedPreceding
231 );
232 assert_eq!(Window::current_row(), FrameBound::CurrentRow);
233 assert_eq!(
234 Window::unbounded_following(),
235 FrameBound::UnboundedFollowing
236 );
237 }
238}