From f8a2cfacfa49c349ddc4e5dad29beaafdea02bfd Mon Sep 17 00:00:00 2001 From: Marat Idrisov Date: Mon, 2 Jan 2023 19:37:19 +0300 Subject: [PATCH 1/6] gh-516: support coexistence of SimpleExpr and QueryStatement --- src/expr.rs | 6 +++++ src/func.rs | 8 +++---- tests/mysql/query.rs | 47 ++++++++++++++++++++++++++++++++++++++ tests/postgres/query.rs | 50 ++++++++++++++++++++++++++++++++++++++++- tests/sqlite/query.rs | 47 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 5 deletions(-) diff --git a/src/expr.rs b/src/expr.rs index 5c9c91474..cfca813c0 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -2163,6 +2163,12 @@ impl Expression for SimpleExpr { } } +impl From for SimpleExpr { + fn from(sel: SelectStatement) -> Self { + SimpleExpr::SubQuery(None, Box::new(sel.into_sub_query_statement())) + } +} + impl SimpleExpr { /// Negates an expression with `NOT`. /// diff --git a/src/func.rs b/src/func.rs index c4da1d4e9..bc08a46d3 100644 --- a/src/func.rs +++ b/src/func.rs @@ -407,7 +407,7 @@ impl Func { /// /// let query = Query::select() /// .expr(Func::coalesce([ - /// Expr::col(Char::SizeW).into(), + /// Query::select().from(Char::Table).expr(Func::max(Expr::col(Char::SizeW))).to_owned().into(), /// Expr::col(Char::SizeH).into(), /// Expr::val(12).into(), /// ])) @@ -416,15 +416,15 @@ impl Func { /// /// assert_eq!( /// query.to_string(MysqlQueryBuilder), - /// r#"SELECT COALESCE(`size_w`, `size_h`, 12) FROM `character`"# + /// r#"SELECT COALESCE((SELECT MAX(`size_w`) FROM `character`), `size_h`, 12) FROM `character`"# /// ); /// assert_eq!( /// query.to_string(PostgresQueryBuilder), - /// r#"SELECT COALESCE("size_w", "size_h", 12) FROM "character""# + /// r#"SELECT COALESCE((SELECT MAX("size_w") FROM "character"), "size_h", 12) FROM "character""# /// ); /// assert_eq!( /// query.to_string(SqliteQueryBuilder), - /// r#"SELECT COALESCE("size_w", "size_h", 12) FROM "character""# + /// r#"SELECT COALESCE((SELECT MAX("size_w") FROM "character"), "size_h", 12) FROM "character""# /// ); /// ``` pub fn coalesce(args: I) -> FunctionCall diff --git a/tests/mysql/query.rs b/tests/mysql/query.rs index 8b285ae79..eeb287f70 100644 --- a/tests/mysql/query.rs +++ b/tests/mysql/query.rs @@ -1005,6 +1005,27 @@ fn select_58() { ); } +#[test] +fn select_coalesce() { + let query = Query::select() + .expr(Func::coalesce([ + Query::select() + .from(Char::Table) + .expr(Func::max(Expr::col(Character::Id))) + .to_owned() + .into(), + 1.into(), + Value::Bool(None).into(), + ])) + .from(Char::Table) + .to_owned(); + + assert_eq!( + query.to_string(MysqlQueryBuilder), + r#"SELECT COALESCE((SELECT MAX(`id`) FROM `character`), 1, NULL) FROM `character`"# + ); +} + #[test] #[allow(clippy::approx_constant)] fn insert_2() { @@ -1272,6 +1293,32 @@ fn insert_on_conflict_4() { ); } +#[test] +fn insert_coalesce() { + assert_eq!(Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Image, Glyph::Aspect]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + Func::coalesce([Query::select() + .from(Glyph::Table) + .expr(Func::max(Expr::col(Glyph::Aspect))) + .to_owned() + .into(), + 1.into(), + Value::Bool(None).into(), + ]) + .into(), + ]) + .to_string(MysqlQueryBuilder), + [ + r#"INSERT INTO `glyph` (`image`, `aspect`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', COALESCE((SELECT MAX(`aspect`) FROM `glyph`), 1, NULL))"#, + ] + .join(" ") + ); +} + #[test] fn update_1() { assert_eq!( diff --git a/tests/postgres/query.rs b/tests/postgres/query.rs index 7422f9266..8a1748785 100644 --- a/tests/postgres/query.rs +++ b/tests/postgres/query.rs @@ -1,6 +1,7 @@ use super::*; use pretty_assertions::assert_eq; use sea_query::extension::postgres::PgBinOper; +use sea_query::Value; #[test] fn select_1() { @@ -103,7 +104,7 @@ fn select_8() { .from(Char::Table) .left_join( Font::Table, - Expr::col((Char::Table, Char::FontId)).equals((Font::Table, Font::Id)) + Expr::col((Char::Table, Char::FontId)).equals((Font::Table, Font::Id)), ) .to_string(PostgresQueryBuilder), r#"SELECT "character" FROM "character" LEFT JOIN "font" ON "character"."font_id" = "font"."id""# @@ -1095,6 +1096,27 @@ fn select_62() { ); } +#[test] +fn select_coalesce() { + let query = Query::select() + .expr(Func::coalesce([ + Query::select() + .from(Char::Table) + .expr(Func::count(Expr::col(Character::Id))) + .to_owned() + .into(), + 1.into(), + Value::Bool(None).into(), + ])) + .from(Char::Table) + .to_owned(); + + assert_eq!( + query.to_string(PostgresQueryBuilder), + r#"SELECT COALESCE((SELECT COUNT("id") FROM "character"), 1, NULL) FROM "character""# + ); +} + #[test] #[allow(clippy::approx_constant)] fn insert_2() { @@ -1396,6 +1418,32 @@ fn insert_returning_specific_columns() { ); } +#[test] +fn insert_coalesce() { + assert_eq!(Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Image, Glyph::Aspect]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + Func::coalesce([Query::select() + .from(Glyph::Table) + .expr(Func::max(Expr::col(Glyph::Aspect))) + .to_owned() + .into(), + 1.into(), + Value::Bool(None).into(), + ]) + .into(), + ]) + .to_string(PostgresQueryBuilder), + [ + r#"INSERT INTO "glyph" ("image", "aspect")"#, + r#"VALUES ('04108048005887010020060000204E0180400400', COALESCE((SELECT MAX("aspect") FROM "glyph"), 1, NULL))"#, + ] + .join(" ") + ); +} + #[test] fn update_1() { assert_eq!( diff --git a/tests/sqlite/query.rs b/tests/sqlite/query.rs index 227c44868..2409a76a7 100644 --- a/tests/sqlite/query.rs +++ b/tests/sqlite/query.rs @@ -1041,6 +1041,27 @@ fn cast_json_field_bin_oper() { ); } +#[test] +fn select_coalesce() { + let query = Query::select() + .expr(Func::coalesce([ + Query::select() + .from(Char::Table) + .expr(Func::max(Expr::col(Character::Id))) + .to_owned() + .into(), + 1.into(), + Value::Bool(None).into(), + ])) + .from(Char::Table) + .to_owned(); + + assert_eq!( + query.to_string(SqliteQueryBuilder), + r#"SELECT COALESCE((SELECT MAX("id") FROM "character"), 1, NULL) FROM "character""# + ); +} + #[test] #[allow(clippy::approx_constant)] fn insert_2() { @@ -1333,6 +1354,32 @@ fn insert_returning_specific_columns() { ); } +#[test] +fn insert_coalesce() { + assert_eq!(Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Image, Glyph::Aspect]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + Func::coalesce([Query::select() + .from(Glyph::Table) + .expr(Func::max(Expr::col(Glyph::Aspect))) + .to_owned() + .into(), + 1.into(), + Value::Bool(None).into(), + ]) + .into(), + ]) + .to_string(SqliteQueryBuilder), + [ + r#"INSERT INTO "glyph" ("image", "aspect")"#, + r#"VALUES ('04108048005887010020060000204E0180400400', COALESCE((SELECT MAX("aspect") FROM "glyph"), 1, NULL))"#, + ] + .join(" ") + ); +} + #[test] fn update_1() { assert_eq!( From 6dc6fc965188c2b3c852511c669fba219a370d2b Mon Sep 17 00:00:00 2001 From: Marat Idrisov Date: Tue, 3 Jan 2023 14:46:19 +0300 Subject: [PATCH 2/6] gh-516: fix formatting --- src/func.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/func.rs b/src/func.rs index bc08a46d3..1d835448f 100644 --- a/src/func.rs +++ b/src/func.rs @@ -407,7 +407,11 @@ impl Func { /// /// let query = Query::select() /// .expr(Func::coalesce([ - /// Query::select().from(Char::Table).expr(Func::max(Expr::col(Char::SizeW))).to_owned().into(), + /// Query::select() + /// .from(Char::Table) + /// .expr(Func::max(Expr::col(Char::SizeW))) + /// .to_owned() + /// .into(), /// Expr::col(Char::SizeH).into(), /// Expr::val(12).into(), /// ])) From bdd64a04a47b53562041c0d2367ea01a20ae031e Mon Sep 17 00:00:00 2001 From: Marat Idrisov Date: Sat, 25 Feb 2023 18:34:17 +0300 Subject: [PATCH 3/6] gh-516: call `.take()` instead of `.to_owned()` --- src/func.rs | 2 +- tests/mysql/query.rs | 2 +- tests/postgres/query.rs | 2 +- tests/sqlite/query.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/func.rs b/src/func.rs index 1d835448f..a6df4edc1 100644 --- a/src/func.rs +++ b/src/func.rs @@ -410,7 +410,7 @@ impl Func { /// Query::select() /// .from(Char::Table) /// .expr(Func::max(Expr::col(Char::SizeW))) - /// .to_owned() + /// .take() /// .into(), /// Expr::col(Char::SizeH).into(), /// Expr::val(12).into(), diff --git a/tests/mysql/query.rs b/tests/mysql/query.rs index eeb287f70..b24487581 100644 --- a/tests/mysql/query.rs +++ b/tests/mysql/query.rs @@ -1303,7 +1303,7 @@ fn insert_coalesce() { Func::coalesce([Query::select() .from(Glyph::Table) .expr(Func::max(Expr::col(Glyph::Aspect))) - .to_owned() + .take() .into(), 1.into(), Value::Bool(None).into(), diff --git a/tests/postgres/query.rs b/tests/postgres/query.rs index 8a1748785..c11ff927f 100644 --- a/tests/postgres/query.rs +++ b/tests/postgres/query.rs @@ -1428,7 +1428,7 @@ fn insert_coalesce() { Func::coalesce([Query::select() .from(Glyph::Table) .expr(Func::max(Expr::col(Glyph::Aspect))) - .to_owned() + .take() .into(), 1.into(), Value::Bool(None).into(), diff --git a/tests/sqlite/query.rs b/tests/sqlite/query.rs index 2409a76a7..a27745d64 100644 --- a/tests/sqlite/query.rs +++ b/tests/sqlite/query.rs @@ -1364,7 +1364,7 @@ fn insert_coalesce() { Func::coalesce([Query::select() .from(Glyph::Table) .expr(Func::max(Expr::col(Glyph::Aspect))) - .to_owned() + .take() .into(), 1.into(), Value::Bool(None).into(), From e5f0b64552cd117d3cac7e0560973d1ef2c7fda6 Mon Sep 17 00:00:00 2001 From: Marat Idrisov Date: Thu, 27 Apr 2023 23:29:04 +0300 Subject: [PATCH 4/6] gh-516: use into_sub_query_expr instead of From --- src/expr.rs | 10 ++++------ src/func.rs | 2 +- src/query/select.rs | 6 ++++++ tests/mysql/query.rs | 6 +++--- tests/postgres/query.rs | 6 +++--- tests/sqlite/query.rs | 4 ++-- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/expr.rs b/src/expr.rs index cfca813c0..1d0d78e46 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -37,6 +37,10 @@ pub enum SimpleExpr { Constant(Value), } +pub trait IntoSimpleExpr { + fn into_sub_query_expr(self) -> SimpleExpr; +} + pub(crate) mod private { use crate::{BinOper, LikeExpr, SimpleExpr, UnOper}; @@ -2163,12 +2167,6 @@ impl Expression for SimpleExpr { } } -impl From for SimpleExpr { - fn from(sel: SelectStatement) -> Self { - SimpleExpr::SubQuery(None, Box::new(sel.into_sub_query_statement())) - } -} - impl SimpleExpr { /// Negates an expression with `NOT`. /// diff --git a/src/func.rs b/src/func.rs index a6df4edc1..0a45fec84 100644 --- a/src/func.rs +++ b/src/func.rs @@ -411,7 +411,7 @@ impl Func { /// .from(Char::Table) /// .expr(Func::max(Expr::col(Char::SizeW))) /// .take() - /// .into(), + /// .into_sub_query_expr(), /// Expr::col(Char::SizeH).into(), /// Expr::val(12).into(), /// ])) diff --git a/src/query/select.rs b/src/query/select.rs index c848809f5..2b1e3f2bd 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -2317,3 +2317,9 @@ impl ConditionalStatement for SelectStatement { self } } + +impl IntoSimpleExpr for SelectStatement { + fn into_sub_query_expr(self) -> SimpleExpr { + SimpleExpr::SubQuery(None, Box::new(self.into_sub_query_statement())) + } +} diff --git a/tests/mysql/query.rs b/tests/mysql/query.rs index b24487581..f62d94d5c 100644 --- a/tests/mysql/query.rs +++ b/tests/mysql/query.rs @@ -1012,8 +1012,8 @@ fn select_coalesce() { Query::select() .from(Char::Table) .expr(Func::max(Expr::col(Character::Id))) - .to_owned() - .into(), + .take() + .into_sub_query_expr(), 1.into(), Value::Bool(None).into(), ])) @@ -1304,7 +1304,7 @@ fn insert_coalesce() { .from(Glyph::Table) .expr(Func::max(Expr::col(Glyph::Aspect))) .take() - .into(), + .into_sub_query_expr(), 1.into(), Value::Bool(None).into(), ]) diff --git a/tests/postgres/query.rs b/tests/postgres/query.rs index c11ff927f..9fad8f4b5 100644 --- a/tests/postgres/query.rs +++ b/tests/postgres/query.rs @@ -1103,8 +1103,8 @@ fn select_coalesce() { Query::select() .from(Char::Table) .expr(Func::count(Expr::col(Character::Id))) - .to_owned() - .into(), + .take() + .into_sub_query_expr(), 1.into(), Value::Bool(None).into(), ])) @@ -1429,7 +1429,7 @@ fn insert_coalesce() { .from(Glyph::Table) .expr(Func::max(Expr::col(Glyph::Aspect))) .take() - .into(), + .into_sub_query_expr(), 1.into(), Value::Bool(None).into(), ]) diff --git a/tests/sqlite/query.rs b/tests/sqlite/query.rs index a27745d64..792b802ff 100644 --- a/tests/sqlite/query.rs +++ b/tests/sqlite/query.rs @@ -1049,7 +1049,7 @@ fn select_coalesce() { .from(Char::Table) .expr(Func::max(Expr::col(Character::Id))) .to_owned() - .into(), + .into_sub_query_expr(), 1.into(), Value::Bool(None).into(), ])) @@ -1365,7 +1365,7 @@ fn insert_coalesce() { .from(Glyph::Table) .expr(Func::max(Expr::col(Glyph::Aspect))) .take() - .into(), + .into_sub_query_expr(), 1.into(), Value::Bool(None).into(), ]) From e88fc4f729b225a636b94e498eac26db4a29c249 Mon Sep 17 00:00:00 2001 From: Marat Idrisov Date: Thu, 27 Apr 2023 23:36:47 +0300 Subject: [PATCH 5/6] gh-516: minor fix --- tests/sqlite/query.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sqlite/query.rs b/tests/sqlite/query.rs index 792b802ff..78d12a1ef 100644 --- a/tests/sqlite/query.rs +++ b/tests/sqlite/query.rs @@ -1048,7 +1048,7 @@ fn select_coalesce() { Query::select() .from(Char::Table) .expr(Func::max(Expr::col(Character::Id))) - .to_owned() + .take() .into_sub_query_expr(), 1.into(), Value::Bool(None).into(), From 0c794020b650c06f25d8cfcf7482a24897dd5e97 Mon Sep 17 00:00:00 2001 From: Marat Idrisov Date: Thu, 27 Apr 2023 23:53:29 +0300 Subject: [PATCH 6/6] gh-516: reorder the tests --- tests/mysql/query.rs | 52 ++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/tests/mysql/query.rs b/tests/mysql/query.rs index 67e083b07..013e5ef04 100644 --- a/tests/mysql/query.rs +++ b/tests/mysql/query.rs @@ -1295,32 +1295,6 @@ fn insert_on_conflict_4() { ); } -#[test] -fn insert_coalesce() { - assert_eq!(Query::insert() - .into_table(Glyph::Table) - .columns([Glyph::Image, Glyph::Aspect]) - .values_panic([ - "04108048005887010020060000204E0180400400".into(), - Func::coalesce([Query::select() - .from(Glyph::Table) - .expr(Func::max(Expr::col(Glyph::Aspect))) - .take() - .into_sub_query_expr(), - 1.into(), - Value::Bool(None).into(), - ]) - .into(), - ]) - .to_string(MysqlQueryBuilder), - [ - r#"INSERT INTO `glyph` (`image`, `aspect`)"#, - r#"VALUES ('04108048005887010020060000204E0180400400', COALESCE((SELECT MAX(`aspect`) FROM `glyph`), 1, NULL))"#, - ] - .join(" ") - ); -} - #[test] #[allow(clippy::approx_constant)] fn insert_on_conflict_5() { @@ -1375,6 +1349,32 @@ fn insert_on_conflict_6() { ); } +#[test] +fn insert_coalesce() { + assert_eq!(Query::insert() + .into_table(Glyph::Table) + .columns([Glyph::Image, Glyph::Aspect]) + .values_panic([ + "04108048005887010020060000204E0180400400".into(), + Func::coalesce([Query::select() + .from(Glyph::Table) + .expr(Func::max(Expr::col(Glyph::Aspect))) + .take() + .into_sub_query_expr(), + 1.into(), + Value::Bool(None).into(), + ]) + .into(), + ]) + .to_string(MysqlQueryBuilder), + [ + r#"INSERT INTO `glyph` (`image`, `aspect`)"#, + r#"VALUES ('04108048005887010020060000204E0180400400', COALESCE((SELECT MAX(`aspect`) FROM `glyph`), 1, NULL))"#, + ] + .join(" ") + ); +} + #[test] fn update_1() { assert_eq!(