diff --git a/src/query/select.rs b/src/query/select.rs index d315e16f2..71288ac4e 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -201,6 +201,82 @@ where } } +impl From for WindowSelectType { + fn from(stmt: WindowStatement) -> Self { + Self::Query(stmt) + } +} + +impl From for WindowSelectType { + fn from(iden: T) -> Self { + Self::Name(iden.into_iden()) + } +} + +/// Extension methods for building a [`SelectExpr`] from an expression. +/// +/// This makes it ergonomic to attach select-specific modifiers (like `AS` and `OVER`) and pass the +/// result into [`SelectStatement::expr`]. +/// +/// # Examples +/// +/// ``` +/// use sea_query::{tests_cfg::*, *}; +/// +/// let query = Query::select() +/// .from(Char::Table) +/// .expr( +/// Expr::col(Char::Character) +/// .max() +/// .over(WindowStatement::partition_by(Char::FontSize)) +/// .alias("C"), +/// ) +/// .to_owned(); +/// +/// assert_eq!( +/// query.to_string(MysqlQueryBuilder), +/// r#"SELECT MAX(`character`) OVER ( PARTITION BY `font_size` ) AS `C` FROM `character`"# +/// ); +/// ``` +pub trait SelectExprTrait: Sized { + fn alias(self, alias: A) -> SelectExpr + where + A: IntoIden; + + fn over(self, over_expr: impl Into) -> SelectExpr; +} + +impl SelectExprTrait for SelectExpr { + fn alias(mut self, alias: A) -> SelectExpr + where + A: IntoIden, + { + self.alias = Some(alias.into_iden()); + self + } + + fn over(mut self, over_expr: impl Into) -> SelectExpr { + self.window = Some(over_expr.into()); + self + } +} + +impl SelectExprTrait for T +where + T: Into, +{ + fn alias(self, alias: A) -> SelectExpr + where + A: IntoIden, + { + SelectExpr::from(self).alias(alias) + } + + fn over(self, over_expr: impl Into) -> SelectExpr { + SelectExpr::from(self).over(over_expr) + } +} + impl SelectStatement { /// Construct a new [`SelectStatement`] pub fn new() -> Self { @@ -678,11 +754,7 @@ impl SelectStatement { T: Into, A: IntoIden, { - self.expr(SelectExpr { - expr: expr.into(), - alias: Some(alias.into_iden()), - window: None, - }); + self.expr(expr.alias(alias)); self } @@ -696,33 +768,29 @@ impl SelectStatement { /// let query = Query::select() /// .from(Char::Table) /// .expr_window( - /// Expr::col(Char::Character), + /// Expr::col(Char::Character).max(), /// WindowStatement::partition_by(Char::FontSize), /// ) /// .to_owned(); /// /// assert_eq!( /// query.to_string(MysqlQueryBuilder), - /// r#"SELECT `character` OVER ( PARTITION BY `font_size` ) FROM `character`"# + /// r#"SELECT MAX(`character`) OVER ( PARTITION BY `font_size` ) FROM `character`"# /// ); /// assert_eq!( /// query.to_string(PostgresQueryBuilder), - /// r#"SELECT "character" OVER ( PARTITION BY "font_size" ) FROM "character""# + /// r#"SELECT MAX("character") OVER ( PARTITION BY "font_size" ) FROM "character""# /// ); /// assert_eq!( /// query.to_string(SqliteQueryBuilder), - /// r#"SELECT "character" OVER ( PARTITION BY "font_size" ) FROM "character""# + /// r#"SELECT MAX("character") OVER ( PARTITION BY "font_size" ) FROM "character""# /// ); /// ``` pub fn expr_window(&mut self, expr: T, window: WindowStatement) -> &mut Self where T: Into, { - self.expr(SelectExpr { - expr: expr.into(), - alias: None, - window: Some(WindowSelectType::Query(window)), - }); + self.expr(expr.over(window)); self } @@ -736,7 +804,7 @@ impl SelectStatement { /// let query = Query::select() /// .from(Char::Table) /// .expr_window_as( - /// Expr::col(Char::Character), + /// Expr::col(Char::Character).max(), /// WindowStatement::partition_by(Char::FontSize), /// "C", /// ) @@ -744,15 +812,15 @@ impl SelectStatement { /// /// assert_eq!( /// query.to_string(MysqlQueryBuilder), - /// r#"SELECT `character` OVER ( PARTITION BY `font_size` ) AS `C` FROM `character`"# + /// r#"SELECT MAX(`character`) OVER ( PARTITION BY `font_size` ) AS `C` FROM `character`"# /// ); /// assert_eq!( /// query.to_string(PostgresQueryBuilder), - /// r#"SELECT "character" OVER ( PARTITION BY "font_size" ) AS "C" FROM "character""# + /// r#"SELECT MAX("character") OVER ( PARTITION BY "font_size" ) AS "C" FROM "character""# /// ); /// assert_eq!( /// query.to_string(SqliteQueryBuilder), - /// r#"SELECT "character" OVER ( PARTITION BY "font_size" ) AS "C" FROM "character""# + /// r#"SELECT MAX("character") OVER ( PARTITION BY "font_size" ) AS "C" FROM "character""# /// ); /// ``` pub fn expr_window_as(&mut self, expr: T, window: WindowStatement, alias: A) -> &mut Self @@ -760,11 +828,7 @@ impl SelectStatement { T: Into, A: IntoIden, { - self.expr(SelectExpr { - expr: expr.into(), - alias: Some(alias.into_iden()), - window: Some(WindowSelectType::Query(window)), - }); + self.expr(expr.over(window).alias(alias)); self } @@ -777,21 +841,21 @@ impl SelectStatement { /// /// let query = Query::select() /// .from(Char::Table) - /// .expr_window_name(Expr::col(Char::Character), "w") + /// .expr_window_name(Expr::col(Char::Character).max(), "w") /// .window("w", WindowStatement::partition_by(Char::FontSize)) /// .to_owned(); /// /// assert_eq!( /// query.to_string(MysqlQueryBuilder), - /// r#"SELECT `character` OVER `w` FROM `character` WINDOW `w` AS (PARTITION BY `font_size`)"# + /// r#"SELECT MAX(`character`) OVER `w` FROM `character` WINDOW `w` AS (PARTITION BY `font_size`)"# /// ); /// assert_eq!( /// query.to_string(PostgresQueryBuilder), - /// r#"SELECT "character" OVER "w" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# + /// r#"SELECT MAX("character") OVER "w" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# /// ); /// assert_eq!( /// query.to_string(SqliteQueryBuilder), - /// r#"SELECT "character" OVER "w" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# + /// r#"SELECT MAX("character") OVER "w" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# /// ); /// ``` pub fn expr_window_name(&mut self, expr: T, window: W) -> &mut Self @@ -799,11 +863,7 @@ impl SelectStatement { T: Into, W: IntoIden, { - self.expr(SelectExpr { - expr: expr.into(), - alias: None, - window: Some(WindowSelectType::Name(window.into_iden())), - }); + self.expr(expr.over(window)); self } @@ -816,21 +876,21 @@ impl SelectStatement { /// /// let query = Query::select() /// .from(Char::Table) - /// .expr_window_name_as(Expr::col(Char::Character), "w", "C") + /// .expr_window_name_as(Expr::col(Char::Character).max(), "w", "C") /// .window("w", WindowStatement::partition_by(Char::FontSize)) /// .to_owned(); /// /// assert_eq!( /// query.to_string(MysqlQueryBuilder), - /// r#"SELECT `character` OVER `w` AS `C` FROM `character` WINDOW `w` AS (PARTITION BY `font_size`)"# + /// r#"SELECT MAX(`character`) OVER `w` AS `C` FROM `character` WINDOW `w` AS (PARTITION BY `font_size`)"# /// ); /// assert_eq!( /// query.to_string(PostgresQueryBuilder), - /// r#"SELECT "character" OVER "w" AS "C" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# + /// r#"SELECT MAX("character") OVER "w" AS "C" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# /// ); /// assert_eq!( /// query.to_string(SqliteQueryBuilder), - /// r#"SELECT "character" OVER "w" AS "C" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# + /// r#"SELECT MAX("character") OVER "w" AS "C" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# /// ); /// ``` pub fn expr_window_name_as(&mut self, expr: T, window: W, alias: A) -> &mut Self @@ -839,11 +899,7 @@ impl SelectStatement { A: IntoIden, W: IntoIden, { - self.expr(SelectExpr { - expr: expr.into(), - alias: Some(alias.into_iden()), - window: Some(WindowSelectType::Name(window.into_iden())), - }); + self.expr(expr.over(window).alias(alias)); self } @@ -2549,21 +2605,21 @@ impl SelectStatement { /// /// let query = Query::select() /// .from(Char::Table) - /// .expr_window_name_as(Expr::col(Char::Character), "w", "C") + /// .expr_window_name_as(Expr::col(Char::Character).max(), "w", "C") /// .window("w", WindowStatement::partition_by(Char::FontSize)) /// .to_owned(); /// /// assert_eq!( /// query.to_string(MysqlQueryBuilder), - /// r#"SELECT `character` OVER `w` AS `C` FROM `character` WINDOW `w` AS (PARTITION BY `font_size`)"# + /// r#"SELECT MAX(`character`) OVER `w` AS `C` FROM `character` WINDOW `w` AS (PARTITION BY `font_size`)"# /// ); /// assert_eq!( /// query.to_string(PostgresQueryBuilder), - /// r#"SELECT "character" OVER "w" AS "C" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# + /// r#"SELECT MAX("character") OVER "w" AS "C" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# /// ); /// assert_eq!( /// query.to_string(SqliteQueryBuilder), - /// r#"SELECT "character" OVER "w" AS "C" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# + /// r#"SELECT MAX("character") OVER "w" AS "C" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# /// ); /// ``` pub fn window(&mut self, name: A, window: WindowStatement) -> &mut Self diff --git a/tests/mysql/query.rs b/tests/mysql/query.rs index b1415a7c9..5aba7ae25 100644 --- a/tests/mysql/query.rs +++ b/tests/mysql/query.rs @@ -447,6 +447,15 @@ fn select_32() { .to_string(MysqlQueryBuilder), "SELECT `character` AS `C` FROM `character`" ); + + // Same SQL as `expr_as`, but expressed via `SelectExprTrait`. + assert_eq!( + Query::select() + .expr(Expr::col(Char::Character).alias("C")) + .from(Char::Table) + .to_string(MysqlQueryBuilder), + "SELECT `character` AS `C` FROM `character`" + ); } #[test] @@ -1042,6 +1051,34 @@ fn select_61() { ); } +#[test] +fn select_62() { + assert_eq!( + Query::select() + .from(Char::Table) + .expr( + Expr::col(Char::Character) + .max() + .over(WindowStatement::partition_by(Char::FontSize)) + .alias("C"), + ) + .to_string(MysqlQueryBuilder), + r#"SELECT MAX(`character`) OVER ( PARTITION BY `font_size` ) AS `C` FROM `character`"# + ); +} + +#[test] +fn select_63() { + assert_eq!( + Query::select() + .from(Char::Table) + .expr(Expr::col(Char::Character).max().over("w")) + .window("w", WindowStatement::partition_by(Char::FontSize)) + .to_string(MysqlQueryBuilder), + r#"SELECT MAX(`character`) OVER `w` FROM `character` WINDOW `w` AS (PARTITION BY `font_size`)"# + ); +} + #[test] fn md5_fn() { assert_eq!( diff --git a/tests/postgres/query.rs b/tests/postgres/query.rs index 10566bbe3..ba70fc013 100644 --- a/tests/postgres/query.rs +++ b/tests/postgres/query.rs @@ -499,6 +499,15 @@ fn select_32() { query.audit_unwrap().selected_tables(), [Char::Table.into_iden()] ); + + // Same SQL as `expr_as`, but expressed via `SelectExprTrait`. + assert_eq!( + Query::select() + .expr(Expr::col(Char::Character).alias("C")) + .from(Char::Table) + .to_string(PostgresQueryBuilder), + r#"SELECT "character" AS "C" FROM "character""# + ); } #[test] @@ -1262,6 +1271,34 @@ fn select_64() { assert_eq!(query.audit_unwrap().selected_tables(), []); } +#[test] +fn select_65() { + assert_eq!( + Query::select() + .from(Char::Table) + .expr( + Expr::col(Char::Character) + .max() + .over(WindowStatement::partition_by(Char::FontSize)) + .alias("C"), + ) + .to_string(PostgresQueryBuilder), + r#"SELECT MAX("character") OVER ( PARTITION BY "font_size" ) AS "C" FROM "character""# + ); +} + +#[test] +fn select_66() { + assert_eq!( + Query::select() + .from(Char::Table) + .expr(Expr::col(Char::Character).max().over("w")) + .window("w", WindowStatement::partition_by(Char::FontSize)) + .to_string(PostgresQueryBuilder), + r#"SELECT MAX("character") OVER "w" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# + ); +} + #[test] #[allow(clippy::approx_constant)] fn insert_2() { diff --git a/tests/sqlite/query.rs b/tests/sqlite/query.rs index a82c2d4e6..2112382ca 100644 --- a/tests/sqlite/query.rs +++ b/tests/sqlite/query.rs @@ -448,6 +448,15 @@ fn select_32() { .to_string(SqliteQueryBuilder), r#"SELECT "character" AS "C" FROM "character""# ); + + // Same SQL as `expr_as`, but expressed via `SelectExprTrait`. + assert_eq!( + Query::select() + .expr(Expr::col(Char::Character).alias("C")) + .from(Char::Table) + .to_string(SqliteQueryBuilder), + r#"SELECT "character" AS "C" FROM "character""# + ); } #[test] @@ -987,6 +996,34 @@ fn select_58() { ); } +#[test] +fn select_59() { + assert_eq!( + Query::select() + .from(Char::Table) + .expr( + Expr::col(Char::Character) + .max() + .over(WindowStatement::partition_by(Char::FontSize)) + .alias("C"), + ) + .to_string(SqliteQueryBuilder), + r#"SELECT MAX("character") OVER ( PARTITION BY "font_size" ) AS "C" FROM "character""# + ); +} + +#[test] +fn select_60() { + assert_eq!( + Query::select() + .from(Char::Table) + .expr(Expr::col(Char::Character).max().over("w")) + .window("w", WindowStatement::partition_by(Char::FontSize)) + .to_string(SqliteQueryBuilder), + r#"SELECT MAX("character") OVER "w" FROM "character" WINDOW "w" AS (PARTITION BY "font_size")"# + ); +} + #[test] fn glob_bin_oper() { assert_eq!(