1use core::marker::PhantomData;
27
28use crate::sql::{SQL, Token};
29use crate::traits::{SQLParam, ToSQL};
30use crate::types::{BooleanLike, Compatible, DataType};
31
32use super::agg::{CountPolicy, FloatPolicy};
33use super::null::NullOr;
34use super::{Agg, Expr, NonNull, Null, Nullability, SQLExpr, Scalar};
35
36#[derive(Debug, Clone, Copy)]
42pub enum FrameBound {
43 UnboundedPreceding,
45 Preceding(u64),
47 CurrentRow,
49 Following(u64),
51 UnboundedFollowing,
53}
54
55impl FrameBound {
56 fn write_sql<'a, V: SQLParam>(&self) -> SQL<'a, V> {
57 match self {
58 Self::UnboundedPreceding => SQL::from(Token::UNBOUNDED).push(Token::PRECEDING),
59 Self::Preceding(n) => {
60 SQL::number(usize::try_from(*n).unwrap_or(usize::MAX)).push(Token::PRECEDING)
61 }
62 Self::CurrentRow => SQL::from(Token::CURRENT).push(Token::ROW),
63 Self::Following(n) => {
64 SQL::number(usize::try_from(*n).unwrap_or(usize::MAX)).push(Token::FOLLOWING)
65 }
66 Self::UnboundedFollowing => SQL::from(Token::UNBOUNDED).push(Token::FOLLOWING),
67 }
68 }
69}
70
71#[derive(Debug, Clone)]
88pub struct WindowSpec<'a, V: SQLParam> {
89 partition: Option<SQL<'a, V>>,
90 order: Option<SQL<'a, V>>,
91 frame: Option<SQL<'a, V>>,
92}
93
94#[must_use]
96pub const fn window<'a, V: SQLParam>() -> WindowSpec<'a, V> {
97 WindowSpec {
98 partition: None,
99 order: None,
100 frame: None,
101 }
102}
103
104impl<'a, V: SQLParam + 'a> WindowSpec<'a, V> {
105 #[must_use]
107 pub fn partition_by<I>(mut self, exprs: I) -> Self
108 where
109 I: IntoIterator,
110 I::Item: ToSQL<'a, V>,
111 {
112 self.partition = Some(
113 SQL::from(Token::PARTITION)
114 .push(Token::BY)
115 .append(SQL::join(exprs, Token::COMMA)),
116 );
117 self
118 }
119
120 #[must_use]
122 pub fn order_by<T: ToSQL<'a, V>>(mut self, exprs: T) -> Self {
123 self.order = Some(
124 SQL::from(Token::ORDER)
125 .push(Token::BY)
126 .append(exprs.into_sql()),
127 );
128 self
129 }
130
131 #[must_use]
133 pub fn rows_between(mut self, start: FrameBound, end: FrameBound) -> Self {
134 self.frame = Some(
135 SQL::from(Token::ROWS)
136 .push(Token::BETWEEN)
137 .append(start.write_sql())
138 .push(Token::AND)
139 .append(end.write_sql()),
140 );
141 self
142 }
143
144 #[must_use]
146 pub fn range_between(mut self, start: FrameBound, end: FrameBound) -> Self {
147 self.frame = Some(
148 SQL::from(Token::RANGE)
149 .push(Token::BETWEEN)
150 .append(start.write_sql())
151 .push(Token::AND)
152 .append(end.write_sql()),
153 );
154 self
155 }
156
157 fn into_sql(self) -> SQL<'a, V> {
159 let mut sql = SQL::empty();
160 if let Some(p) = self.partition {
161 sql.append_mut(p);
162 }
163 if let Some(o) = self.order {
164 sql.append_mut(o);
165 }
166 if let Some(f) = self.frame {
167 sql.append_mut(f);
168 }
169 sql
170 }
171}
172
173impl<'a, V, T, N> SQLExpr<'a, V, T, N, Agg>
178where
179 V: SQLParam + 'a,
180 T: DataType,
181 N: Nullability,
182{
183 pub fn over(self, spec: WindowSpec<'a, V>) -> SQLExpr<'a, V, T, N, Scalar> {
200 let sql = self
201 .into_sql()
202 .push(Token::OVER)
203 .push(Token::LPAREN)
204 .append(spec.into_sql())
205 .push(Token::RPAREN);
206 SQLExpr::new(sql)
207 }
208
209 #[must_use]
213 pub fn filter<C>(self, condition: C) -> Self
214 where
215 C: Expr<'a, V>,
216 C::SQLType: BooleanLike,
217 {
218 let sql = self
219 .into_sql()
220 .push(Token::FILTER)
221 .push(Token::LPAREN)
222 .push(Token::WHERE)
223 .append(condition.into_sql())
224 .push(Token::RPAREN);
225 SQLExpr::new(sql)
226 }
227}
228
229#[derive(Debug, Clone)]
239pub struct WindowFnExpr<'a, V: SQLParam, T: DataType, N: Nullability> {
240 sql: SQL<'a, V>,
241 _marker: PhantomData<(T, N)>,
242}
243
244impl<'a, V, T, N> WindowFnExpr<'a, V, T, N>
245where
246 V: SQLParam + 'a,
247 T: DataType,
248 N: Nullability,
249{
250 const fn new(sql: SQL<'a, V>) -> Self {
251 Self {
252 sql,
253 _marker: PhantomData,
254 }
255 }
256
257 pub fn over(self, spec: WindowSpec<'a, V>) -> SQLExpr<'a, V, T, N, Scalar> {
261 let sql = self
262 .sql
263 .push(Token::OVER)
264 .push(Token::LPAREN)
265 .append(spec.into_sql())
266 .push(Token::RPAREN);
267 SQLExpr::new(sql)
268 }
269}
270
271#[must_use]
279pub fn row_number<'a, V>() -> WindowFnExpr<'a, V, <V::DialectMarker as CountPolicy>::Count, NonNull>
280where
281 V: SQLParam + 'a,
282 V::DialectMarker: CountPolicy,
283{
284 WindowFnExpr::new(SQL::raw("ROW_NUMBER()"))
285}
286
287#[must_use]
291pub fn rank<'a, V>() -> WindowFnExpr<'a, V, <V::DialectMarker as CountPolicy>::Count, NonNull>
292where
293 V: SQLParam + 'a,
294 V::DialectMarker: CountPolicy,
295{
296 WindowFnExpr::new(SQL::raw("RANK()"))
297}
298
299#[must_use]
303pub fn dense_rank<'a, V>() -> WindowFnExpr<'a, V, <V::DialectMarker as CountPolicy>::Count, NonNull>
304where
305 V: SQLParam + 'a,
306 V::DialectMarker: CountPolicy,
307{
308 WindowFnExpr::new(SQL::raw("DENSE_RANK()"))
309}
310
311#[must_use]
315pub fn ntile<'a, V>(
316 n: usize,
317) -> WindowFnExpr<'a, V, <V::DialectMarker as CountPolicy>::Count, NonNull>
318where
319 V: SQLParam + 'a,
320 V::DialectMarker: CountPolicy,
321{
322 WindowFnExpr::new(SQL::func("NTILE", SQL::number(n)))
323}
324
325#[must_use]
329pub fn percent_rank<'a, V>()
330-> WindowFnExpr<'a, V, <V::DialectMarker as FloatPolicy>::Float, NonNull>
331where
332 V: SQLParam + 'a,
333 V::DialectMarker: FloatPolicy,
334{
335 WindowFnExpr::new(SQL::raw("PERCENT_RANK()"))
336}
337
338#[must_use]
342pub fn cume_dist<'a, V>() -> WindowFnExpr<'a, V, <V::DialectMarker as FloatPolicy>::Float, NonNull>
343where
344 V: SQLParam + 'a,
345 V::DialectMarker: FloatPolicy,
346{
347 WindowFnExpr::new(SQL::raw("CUME_DIST()"))
348}
349
350pub fn lag<'a, V, E>(expr: E) -> WindowFnExpr<'a, V, E::SQLType, Null>
354where
355 V: SQLParam + 'a,
356 E: Expr<'a, V>,
357{
358 WindowFnExpr::new(SQL::func("LAG", expr.into_sql()))
359}
360
361pub fn lag_with_default<'a, V, E, D>(
365 expr: E,
366 offset: usize,
367 default: D,
368) -> WindowFnExpr<'a, V, E::SQLType, <E::Nullable as NullOr<D::Nullable>>::Output>
369where
370 V: SQLParam + 'a,
371 E: Expr<'a, V>,
372 D: Expr<'a, V>,
373 E::SQLType: Compatible<D::SQLType>,
374 E::Nullable: NullOr<D::Nullable>,
375 D::Nullable: Nullability,
376{
377 let args = expr
378 .into_sql()
379 .push(Token::COMMA)
380 .append(SQL::number(offset))
381 .push(Token::COMMA)
382 .append(default.into_sql());
383 WindowFnExpr::new(SQL::func("LAG", args))
384}
385
386pub fn lead<'a, V, E>(expr: E) -> WindowFnExpr<'a, V, E::SQLType, Null>
390where
391 V: SQLParam + 'a,
392 E: Expr<'a, V>,
393{
394 WindowFnExpr::new(SQL::func("LEAD", expr.into_sql()))
395}
396
397pub fn lead_with_default<'a, V, E, D>(
401 expr: E,
402 offset: usize,
403 default: D,
404) -> WindowFnExpr<'a, V, E::SQLType, <E::Nullable as NullOr<D::Nullable>>::Output>
405where
406 V: SQLParam + 'a,
407 E: Expr<'a, V>,
408 D: Expr<'a, V>,
409 E::SQLType: Compatible<D::SQLType>,
410 E::Nullable: NullOr<D::Nullable>,
411 D::Nullable: Nullability,
412{
413 let args = expr
414 .into_sql()
415 .push(Token::COMMA)
416 .append(SQL::number(offset))
417 .push(Token::COMMA)
418 .append(default.into_sql());
419 WindowFnExpr::new(SQL::func("LEAD", args))
420}
421
422pub fn first_value<'a, V, E>(expr: E) -> WindowFnExpr<'a, V, E::SQLType, Null>
426where
427 V: SQLParam + 'a,
428 E: Expr<'a, V>,
429{
430 WindowFnExpr::new(SQL::func("FIRST_VALUE", expr.into_sql()))
431}
432
433pub fn last_value<'a, V, E>(expr: E) -> WindowFnExpr<'a, V, E::SQLType, Null>
437where
438 V: SQLParam + 'a,
439 E: Expr<'a, V>,
440{
441 WindowFnExpr::new(SQL::func("LAST_VALUE", expr.into_sql()))
442}
443
444pub fn nth_value<'a, V, E>(expr: E, n: usize) -> WindowFnExpr<'a, V, E::SQLType, Null>
448where
449 V: SQLParam + 'a,
450 E: Expr<'a, V>,
451{
452 let args = expr.into_sql().push(Token::COMMA).append(SQL::number(n));
453 WindowFnExpr::new(SQL::func("NTH_VALUE", args))
454}