use core::marker::PhantomData;
use crate::sql::{SQL, Token};
use crate::traits::{SQLParam, ToSQL};
use crate::types::{BooleanLike, Compatible, DataType};
use super::agg::{CountPolicy, FloatPolicy};
use super::null::NullOr;
use super::{Agg, Expr, NonNull, Null, Nullability, SQLExpr, Scalar};
#[derive(Debug, Clone, Copy)]
pub enum FrameBound {
UnboundedPreceding,
Preceding(u64),
CurrentRow,
Following(u64),
UnboundedFollowing,
}
impl FrameBound {
fn write_sql<'a, V: SQLParam>(&self) -> SQL<'a, V> {
match self {
Self::UnboundedPreceding => SQL::from(Token::UNBOUNDED).push(Token::PRECEDING),
Self::Preceding(n) => {
SQL::number(usize::try_from(*n).unwrap_or(usize::MAX)).push(Token::PRECEDING)
}
Self::CurrentRow => SQL::from(Token::CURRENT).push(Token::ROW),
Self::Following(n) => {
SQL::number(usize::try_from(*n).unwrap_or(usize::MAX)).push(Token::FOLLOWING)
}
Self::UnboundedFollowing => SQL::from(Token::UNBOUNDED).push(Token::FOLLOWING),
}
}
}
#[derive(Debug, Clone)]
pub struct WindowSpec<'a, V: SQLParam> {
partition: Option<SQL<'a, V>>,
order: Option<SQL<'a, V>>,
frame: Option<SQL<'a, V>>,
}
#[must_use]
pub const fn window<'a, V: SQLParam>() -> WindowSpec<'a, V> {
WindowSpec {
partition: None,
order: None,
frame: None,
}
}
impl<'a, V: SQLParam + 'a> WindowSpec<'a, V> {
#[must_use]
pub fn partition_by<I>(mut self, exprs: I) -> Self
where
I: IntoIterator,
I::Item: ToSQL<'a, V>,
{
self.partition = Some(
SQL::from(Token::PARTITION)
.push(Token::BY)
.append(SQL::join(exprs, Token::COMMA)),
);
self
}
#[must_use]
pub fn order_by<T: ToSQL<'a, V>>(mut self, exprs: T) -> Self {
self.order = Some(
SQL::from(Token::ORDER)
.push(Token::BY)
.append(exprs.into_sql()),
);
self
}
#[must_use]
pub fn rows_between(mut self, start: FrameBound, end: FrameBound) -> Self {
self.frame = Some(
SQL::from(Token::ROWS)
.push(Token::BETWEEN)
.append(start.write_sql())
.push(Token::AND)
.append(end.write_sql()),
);
self
}
#[must_use]
pub fn range_between(mut self, start: FrameBound, end: FrameBound) -> Self {
self.frame = Some(
SQL::from(Token::RANGE)
.push(Token::BETWEEN)
.append(start.write_sql())
.push(Token::AND)
.append(end.write_sql()),
);
self
}
fn into_sql(self) -> SQL<'a, V> {
let mut sql = SQL::empty();
if let Some(p) = self.partition {
sql.append_mut(p);
}
if let Some(o) = self.order {
sql.append_mut(o);
}
if let Some(f) = self.frame {
sql.append_mut(f);
}
sql
}
}
impl<'a, V, T, N> SQLExpr<'a, V, T, N, Agg>
where
V: SQLParam + 'a,
T: DataType,
N: Nullability,
{
pub fn over(self, spec: WindowSpec<'a, V>) -> SQLExpr<'a, V, T, N, Scalar> {
let sql = self
.into_sql()
.push(Token::OVER)
.push(Token::LPAREN)
.append(spec.into_sql())
.push(Token::RPAREN);
SQLExpr::new(sql)
}
#[must_use]
pub fn filter<C>(self, condition: C) -> Self
where
C: Expr<'a, V>,
C::SQLType: BooleanLike,
{
let sql = self
.into_sql()
.push(Token::FILTER)
.push(Token::LPAREN)
.push(Token::WHERE)
.append(condition.into_sql())
.push(Token::RPAREN);
SQLExpr::new(sql)
}
}
#[derive(Debug, Clone)]
pub struct WindowFnExpr<'a, V: SQLParam, T: DataType, N: Nullability> {
sql: SQL<'a, V>,
_marker: PhantomData<(T, N)>,
}
impl<'a, V, T, N> WindowFnExpr<'a, V, T, N>
where
V: SQLParam + 'a,
T: DataType,
N: Nullability,
{
const fn new(sql: SQL<'a, V>) -> Self {
Self {
sql,
_marker: PhantomData,
}
}
pub fn over(self, spec: WindowSpec<'a, V>) -> SQLExpr<'a, V, T, N, Scalar> {
let sql = self
.sql
.push(Token::OVER)
.push(Token::LPAREN)
.append(spec.into_sql())
.push(Token::RPAREN);
SQLExpr::new(sql)
}
}
#[must_use]
pub fn row_number<'a, V>() -> WindowFnExpr<'a, V, <V::DialectMarker as CountPolicy>::Count, NonNull>
where
V: SQLParam + 'a,
V::DialectMarker: CountPolicy,
{
WindowFnExpr::new(SQL::raw("ROW_NUMBER()"))
}
#[must_use]
pub fn rank<'a, V>() -> WindowFnExpr<'a, V, <V::DialectMarker as CountPolicy>::Count, NonNull>
where
V: SQLParam + 'a,
V::DialectMarker: CountPolicy,
{
WindowFnExpr::new(SQL::raw("RANK()"))
}
#[must_use]
pub fn dense_rank<'a, V>() -> WindowFnExpr<'a, V, <V::DialectMarker as CountPolicy>::Count, NonNull>
where
V: SQLParam + 'a,
V::DialectMarker: CountPolicy,
{
WindowFnExpr::new(SQL::raw("DENSE_RANK()"))
}
#[must_use]
pub fn ntile<'a, V>(
n: usize,
) -> WindowFnExpr<'a, V, <V::DialectMarker as CountPolicy>::Count, NonNull>
where
V: SQLParam + 'a,
V::DialectMarker: CountPolicy,
{
WindowFnExpr::new(SQL::func("NTILE", SQL::number(n)))
}
#[must_use]
pub fn percent_rank<'a, V>()
-> WindowFnExpr<'a, V, <V::DialectMarker as FloatPolicy>::Float, NonNull>
where
V: SQLParam + 'a,
V::DialectMarker: FloatPolicy,
{
WindowFnExpr::new(SQL::raw("PERCENT_RANK()"))
}
#[must_use]
pub fn cume_dist<'a, V>() -> WindowFnExpr<'a, V, <V::DialectMarker as FloatPolicy>::Float, NonNull>
where
V: SQLParam + 'a,
V::DialectMarker: FloatPolicy,
{
WindowFnExpr::new(SQL::raw("CUME_DIST()"))
}
pub fn lag<'a, V, E>(expr: E) -> WindowFnExpr<'a, V, E::SQLType, Null>
where
V: SQLParam + 'a,
E: Expr<'a, V>,
{
WindowFnExpr::new(SQL::func("LAG", expr.into_sql()))
}
pub fn lag_with_default<'a, V, E, D>(
expr: E,
offset: usize,
default: D,
) -> WindowFnExpr<'a, V, E::SQLType, <E::Nullable as NullOr<D::Nullable>>::Output>
where
V: SQLParam + 'a,
E: Expr<'a, V>,
D: Expr<'a, V>,
E::SQLType: Compatible<D::SQLType>,
E::Nullable: NullOr<D::Nullable>,
D::Nullable: Nullability,
{
let args = expr
.into_sql()
.push(Token::COMMA)
.append(SQL::number(offset))
.push(Token::COMMA)
.append(default.into_sql());
WindowFnExpr::new(SQL::func("LAG", args))
}
pub fn lead<'a, V, E>(expr: E) -> WindowFnExpr<'a, V, E::SQLType, Null>
where
V: SQLParam + 'a,
E: Expr<'a, V>,
{
WindowFnExpr::new(SQL::func("LEAD", expr.into_sql()))
}
pub fn lead_with_default<'a, V, E, D>(
expr: E,
offset: usize,
default: D,
) -> WindowFnExpr<'a, V, E::SQLType, <E::Nullable as NullOr<D::Nullable>>::Output>
where
V: SQLParam + 'a,
E: Expr<'a, V>,
D: Expr<'a, V>,
E::SQLType: Compatible<D::SQLType>,
E::Nullable: NullOr<D::Nullable>,
D::Nullable: Nullability,
{
let args = expr
.into_sql()
.push(Token::COMMA)
.append(SQL::number(offset))
.push(Token::COMMA)
.append(default.into_sql());
WindowFnExpr::new(SQL::func("LEAD", args))
}
pub fn first_value<'a, V, E>(expr: E) -> WindowFnExpr<'a, V, E::SQLType, Null>
where
V: SQLParam + 'a,
E: Expr<'a, V>,
{
WindowFnExpr::new(SQL::func("FIRST_VALUE", expr.into_sql()))
}
pub fn last_value<'a, V, E>(expr: E) -> WindowFnExpr<'a, V, E::SQLType, Null>
where
V: SQLParam + 'a,
E: Expr<'a, V>,
{
WindowFnExpr::new(SQL::func("LAST_VALUE", expr.into_sql()))
}
pub fn nth_value<'a, V, E>(expr: E, n: usize) -> WindowFnExpr<'a, V, E::SQLType, Null>
where
V: SQLParam + 'a,
E: Expr<'a, V>,
{
let args = expr.into_sql().push(Token::COMMA).append(SQL::number(n));
WindowFnExpr::new(SQL::func("NTH_VALUE", args))
}