use crate::{expr::*, query::*, types::*};
pub trait OverStatement {
#[doc(hidden)]
// Implementation for the trait.
fn add_partition_by(&mut self, partition: SimpleExpr) -> &mut Self;
/// Partition by column.
fn partition_by<T>(&mut self, col: T) -> &mut Self
where
T: IntoColumnRef,
{
self.add_partition_by(SimpleExpr::Column(col.into_column_ref()))
}
/// Partition by custom string.
fn partition_by_customs<I, T>(&mut self, cols: I) -> &mut Self
where
T: ToString,
I: IntoIterator<Item = T>,
{
cols.into_iter().for_each(|c| {
self.add_partition_by(SimpleExpr::Custom(c.to_string()));
});
self
}
/// Partition by vector of columns.
fn partition_by_columns<I, T>(&mut self, cols: I) -> &mut Self
where
T: IntoColumnRef,
I: IntoIterator<Item = T>,
{
cols.into_iter().for_each(|c| {
self.add_partition_by(SimpleExpr::Column(c.into_column_ref()));
});
self
}
}
/// frame_start or frame_end clause
#[derive(Debug, Clone)]
pub enum Frame {
UnboundedPreceding,
Preceding(u32),
CurrentRow,
Following(u32),
UnboundedFollowing,
}
/// Frame type
#[derive(Debug, Clone)]
pub enum FrameType {
Range,
Rows,
}
/// Frame clause
#[derive(Debug, Clone)]
pub struct FrameClause {
pub(crate) r#type: FrameType,
pub(crate) start: Frame,
pub(crate) end: Option<Frame>,
}
/// Window expression
///
/// # References:
///
/// 1. <https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html>
/// 2. <https://www.sqlite.org/windowfunctions.html>
/// 3. <https://www.postgresql.org/docs/current/tutorial-window.html>
#[derive(Default, Debug, Clone)]
pub struct WindowStatement {
pub(crate) partition_by: Vec<SimpleExpr>,
pub(crate) order_by: Vec<OrderExpr>,
pub(crate) frame: Option<FrameClause>,
}
impl WindowStatement {
/// Construct a new [`WindowStatement`]
pub fn new() -> Self {
Self::default()
}
pub fn take(&mut self) -> Self {
Self {
partition_by: std::mem::take(&mut self.partition_by),
order_by: std::mem::take(&mut self.order_by),
frame: self.frame.take(),
}
}
/// Construct a new [`WindowStatement`] with PARTITION BY column
pub fn partition_by<T>(col: T) -> Self
where
T: IntoColumnRef,
{
let mut window = Self::new();
window.add_partition_by(SimpleExpr::Column(col.into_column_ref()));
window
}
/// Construct a new [`WindowStatement`] with PARTITION BY custom
pub fn partition_by_custom<T>(col: T) -> Self
where
T: ToString,
{
let mut window = Self::new();
window.add_partition_by(SimpleExpr::Custom(col.to_string()));
window
}
/// Construct a new [`WindowStatement`] with ORDER BY column
pub fn order_by<T>(col: T, order: Order) -> Self
where
T: IntoColumnRef,
{
let mut window = Self::new();
window.order_by_expr(SimpleExpr::Column(col.into_column_ref()), order);
window
}
/// Construct a new [`WindowStatement`] with ORDER BY custom
pub fn order_by_custom<T>(col: T, order: Order) -> Self
where
T: ToString,
{
let mut window = Self::new();
window.order_by_expr(SimpleExpr::Custom(col.to_string()), order);
window
}
/// frame clause for frame_start
/// # Examples:
///
/// ```
/// use sea_query::{tests_cfg::*, *};
///
/// let query = Query::select()
/// .from(Char::Table)
/// .expr_window_as(
/// Expr::col(Char::Character),
/// WindowStatement::partition_by(Char::FontSize)
/// .frame_start(FrameType::Rows, Frame::UnboundedPreceding)
/// .take(),
/// Alias::new("C"))
/// .to_owned();
///
/// assert_eq!(
/// query.to_string(MysqlQueryBuilder),
/// r#"SELECT `character` OVER ( PARTITION BY `font_size` ROWS UNBOUNDED PRECEDING ) AS `C` FROM `character`"#
/// );
/// assert_eq!(
/// query.to_string(PostgresQueryBuilder),
/// r#"SELECT "character" OVER ( PARTITION BY "font_size" ROWS UNBOUNDED PRECEDING ) AS "C" FROM "character""#
/// );
/// assert_eq!(
/// query.to_string(SqliteQueryBuilder),
/// r#"SELECT "character" OVER ( PARTITION BY "font_size" ROWS UNBOUNDED PRECEDING ) AS "C" FROM "character""#
/// );
/// ```
pub fn frame_start(&mut self, r#type: FrameType, start: Frame) -> &mut Self {
self.frame(r#type, start, None)
}
/// frame clause for BETWEEN frame_start AND frame_end
///
/// # Examples:
///
/// ```
/// use sea_query::{tests_cfg::*, *};
///
/// let query = Query::select()
/// .from(Char::Table)
/// .expr_window_as(
/// Expr::col(Char::Character),
/// WindowStatement::partition_by(Char::FontSize)
/// .frame_between(FrameType::Rows, Frame::UnboundedPreceding, Frame::UnboundedFollowing)
/// .take(),
/// Alias::new("C"))
/// .to_owned();
///
/// assert_eq!(
/// query.to_string(MysqlQueryBuilder),
/// r#"SELECT `character` OVER ( PARTITION BY `font_size` ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS `C` FROM `character`"#
/// );
/// assert_eq!(
/// query.to_string(PostgresQueryBuilder),
/// r#"SELECT "character" OVER ( PARTITION BY "font_size" ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS "C" FROM "character""#
/// );
/// assert_eq!(
/// query.to_string(SqliteQueryBuilder),
/// r#"SELECT "character" OVER ( PARTITION BY "font_size" ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS "C" FROM "character""#
/// );
/// ```
pub fn frame_between(&mut self, r#type: FrameType, start: Frame, end: Frame) -> &mut Self {
self.frame(r#type, start, Some(end))
}
/// frame clause
pub fn frame(&mut self, r#type: FrameType, start: Frame, end: Option<Frame>) -> &mut Self {
let frame_clause = FrameClause { r#type, start, end };
self.frame = Some(frame_clause);
self
}
}
impl OverStatement for WindowStatement {
fn add_partition_by(&mut self, partition: SimpleExpr) -> &mut Self {
self.partition_by.push(partition);
self
}
}
impl OrderedStatement for WindowStatement {
fn add_order_by(&mut self, order: OrderExpr) -> &mut Self {
self.order_by.push(order);
self
}
fn clear_order_by(&mut self) -> &mut Self {
self.order_by = Vec::new();
self
}
}