diff --git a/sea-query-binder/src/sqlx.rs b/sea-query-binder/src/sqlx.rs index ee1990295..6302a302f 100644 --- a/sea-query-binder/src/sqlx.rs +++ b/sea-query-binder/src/sqlx.rs @@ -25,4 +25,3 @@ impl_sqlx_binder!(SelectStatement); impl_sqlx_binder!(UpdateStatement); impl_sqlx_binder!(InsertStatement); impl_sqlx_binder!(DeleteStatement); -impl_sqlx_binder!(WithQuery); diff --git a/sea-query-diesel/src/lib.rs b/sea-query-diesel/src/lib.rs index 1891853a3..52e323282 100644 --- a/sea-query-diesel/src/lib.rs +++ b/sea-query-diesel/src/lib.rs @@ -2,7 +2,6 @@ use diesel::backend::Backend; use diesel::result::QueryResult; use sea_query::{ DeleteStatement, InsertStatement, QueryStatementWriter, SelectStatement, UpdateStatement, - WithQuery, }; use self::backend::{ExtractBuilder, TransformValue}; @@ -40,4 +39,3 @@ impl_diesel_binder!(SelectStatement); impl_diesel_binder!(UpdateStatement); impl_diesel_binder!(InsertStatement); impl_diesel_binder!(DeleteStatement); -impl_diesel_binder!(WithQuery); diff --git a/sea-query-postgres/src/lib.rs b/sea-query-postgres/src/lib.rs index 130567457..be1750b97 100644 --- a/sea-query-postgres/src/lib.rs +++ b/sea-query-postgres/src/lib.rs @@ -47,7 +47,6 @@ impl_postgres_binder!(SelectStatement); impl_postgres_binder!(UpdateStatement); impl_postgres_binder!(InsertStatement); impl_postgres_binder!(DeleteStatement); -impl_postgres_binder!(WithQuery); impl ToSql for PostgresValue { fn to_sql( diff --git a/sea-query-rbatis/src/lib.rs b/sea-query-rbatis/src/lib.rs index 01c6a918e..c4899aa9b 100644 --- a/sea-query-rbatis/src/lib.rs +++ b/sea-query-rbatis/src/lib.rs @@ -26,7 +26,7 @@ impl_rbs_binder!(SelectStatement); impl_rbs_binder!(UpdateStatement); impl_rbs_binder!(InsertStatement); impl_rbs_binder!(DeleteStatement); -impl_rbs_binder!(WithQuery); + trait ToRbV { fn to(self) -> RbValue; } @@ -79,19 +79,35 @@ fn to_rb_values(values: Values) -> Vec { } #[cfg(feature = "with-chrono")] Value::ChronoDateTime(t) => { - args.push(Value::ChronoDateTime(t).chrono_as_naive_utc_in_string().to()); + args.push( + Value::ChronoDateTime(t) + .chrono_as_naive_utc_in_string() + .to(), + ); } #[cfg(feature = "with-chrono")] Value::ChronoDateTimeUtc(t) => { - args.push(Value::ChronoDateTimeUtc(t).chrono_as_naive_utc_in_string().to()); + args.push( + Value::ChronoDateTimeUtc(t) + .chrono_as_naive_utc_in_string() + .to(), + ); } #[cfg(feature = "with-chrono")] Value::ChronoDateTimeLocal(t) => { - args.push(Value::ChronoDateTimeLocal(t).chrono_as_naive_utc_in_string().to()); + args.push( + Value::ChronoDateTimeLocal(t) + .chrono_as_naive_utc_in_string() + .to(), + ); } #[cfg(feature = "with-chrono")] Value::ChronoDateTimeWithTimeZone(t) => { - args.push(Value::ChronoDateTimeWithTimeZone(t).chrono_as_naive_utc_in_string().to()); + args.push( + Value::ChronoDateTimeWithTimeZone(t) + .chrono_as_naive_utc_in_string() + .to(), + ); } #[cfg(feature = "with-time")] Value::TimeDate(t) => { @@ -107,7 +123,11 @@ fn to_rb_values(values: Values) -> Vec { } #[cfg(feature = "with-time")] Value::TimeDateTimeWithTimeZone(t) => { - args.push(Value::TimeDateTimeWithTimeZone(t).time_as_naive_utc_in_string().to()); + args.push( + Value::TimeDateTimeWithTimeZone(t) + .time_as_naive_utc_in_string() + .to(), + ); } #[cfg(feature = "with-uuid")] Value::Uuid(uuid) => { diff --git a/sea-query-rusqlite/src/lib.rs b/sea-query-rusqlite/src/lib.rs index 03d9918ad..c648823a8 100644 --- a/sea-query-rusqlite/src/lib.rs +++ b/sea-query-rusqlite/src/lib.rs @@ -47,7 +47,6 @@ impl_rusqlite_binder!(SelectStatement); impl_rusqlite_binder!(UpdateStatement); impl_rusqlite_binder!(InsertStatement); impl_rusqlite_binder!(DeleteStatement); -impl_rusqlite_binder!(WithQuery); impl ToSql for RusqliteValue { fn to_sql(&self) -> Result> { diff --git a/src/backend/query_builder.rs b/src/backend/query_builder.rs index da0eea675..0872d39cc 100644 --- a/src/backend/query_builder.rs +++ b/src/backend/query_builder.rs @@ -18,6 +18,10 @@ pub trait QueryBuilder: /// Translate [`InsertStatement`] into SQL statement. fn prepare_insert_statement(&self, insert: &InsertStatement, sql: &mut dyn SqlWriter) { + if let Some(with) = &insert.with { + self.prepare_with_clause(with, sql); + } + self.prepare_insert(insert.replace, sql); if let Some(table) = &insert.table { @@ -95,6 +99,9 @@ pub trait QueryBuilder: /// Translate [`SelectStatement`] into SQL statement. fn prepare_select_statement(&self, select: &SelectStatement, sql: &mut dyn SqlWriter) { + if let Some(with) = select.with.as_ref() { + self.prepare_with_clause(with, sql); + } write!(sql, "SELECT ").unwrap(); if let Some(distinct) = &select.distinct { @@ -191,6 +198,10 @@ pub trait QueryBuilder: /// Translate [`UpdateStatement`] into SQL statement. fn prepare_update_statement(&self, update: &UpdateStatement, sql: &mut dyn SqlWriter) { + if let Some(with) = &update.with { + self.prepare_with_clause(with, sql); + } + write!(sql, "UPDATE ").unwrap(); if let Some(table) = &update.table { @@ -245,6 +256,10 @@ pub trait QueryBuilder: /// Translate [`DeleteStatement`] into SQL statement. fn prepare_delete_statement(&self, delete: &DeleteStatement, sql: &mut dyn SqlWriter) { + if let Some(with) = &delete.with { + self.prepare_with_clause(with, sql); + } + write!(sql, "DELETE ").unwrap(); if let Some(table) = &delete.table { @@ -701,11 +716,6 @@ pub trait QueryBuilder: /// Translate [`QueryStatement`] into SQL statement. fn prepare_query_statement(&self, query: &SubQueryStatement, sql: &mut dyn SqlWriter); - fn prepare_with_query(&self, query: &WithQuery, sql: &mut dyn SqlWriter) { - self.prepare_with_clause(&query.with_clause, sql); - self.prepare_query_statement(query.query.as_ref().unwrap().deref(), sql); - } - fn prepare_with_clause(&self, with_clause: &WithClause, sql: &mut dyn SqlWriter) { self.prepare_with_clause_start(with_clause, sql); self.prepare_with_clause_common_tables(with_clause, sql); @@ -1521,7 +1531,6 @@ impl SubQueryStatement { InsertStatement(stmt) => query_builder.prepare_insert_statement(stmt, sql), UpdateStatement(stmt) => query_builder.prepare_update_statement(stmt, sql), DeleteStatement(stmt) => query_builder.prepare_delete_statement(stmt, sql), - WithStatement(stmt) => query_builder.prepare_with_query(stmt, sql), } } } diff --git a/src/query/delete.rs b/src/query/delete.rs index 16b64b615..85b4db779 100644 --- a/src/query/delete.rs +++ b/src/query/delete.rs @@ -5,7 +5,7 @@ use crate::{ types::*, value::*, QueryStatementBuilder, QueryStatementWriter, ReturningClause, SimpleExpr, SubQueryStatement, - WithClause, WithQuery, + WithClause, }; use inherent::inherent; @@ -44,6 +44,7 @@ pub struct DeleteStatement { pub(crate) orders: Vec, pub(crate) limit: Option, pub(crate) returning: Option, + pub(crate) with: Option, } impl DeleteStatement { @@ -186,7 +187,7 @@ impl DeleteStatement { self.returning(ReturningClause::All) } - /// Create a [WithQuery] by specifying a [WithClause] to execute this query with. + /// Specify a [WithClause] to execute this query with. /// /// # Examples /// @@ -204,11 +205,11 @@ impl DeleteStatement { /// .table_name(Alias::new("cte")) /// .to_owned(); /// let with_clause = WithClause::new().cte(cte).to_owned(); - /// let update = DeleteStatement::new() + /// let query = DeleteStatement::new() /// .from_table(Glyph::Table) /// .and_where(Expr::col(Glyph::Id).in_subquery(SelectStatement::new().column(Glyph::Id).from(Alias::new("cte")).to_owned())) + /// .with(with_clause) /// .to_owned(); - /// let query = update.with(with_clause); /// /// assert_eq!( /// query.to_string(MysqlQueryBuilder), @@ -223,8 +224,9 @@ impl DeleteStatement { /// r#"WITH "cte" ("id") AS (SELECT "id" FROM "glyph" WHERE "image" LIKE '0%') DELETE FROM "glyph" WHERE "id" IN (SELECT "id" FROM "cte")"# /// ); /// ``` - pub fn with(self, clause: WithClause) -> WithQuery { - clause.query(self) + pub fn with(&mut self, clause: WithClause) -> &mut Self { + self.with = Some(clause); + self } } diff --git a/src/query/insert.rs b/src/query/insert.rs index b82f53611..6b53cc8df 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -1,7 +1,7 @@ use crate::{ backend::QueryBuilder, error::*, prepare::*, types::*, OnConflict, QueryStatementBuilder, QueryStatementWriter, ReturningClause, SelectStatement, SimpleExpr, SubQueryStatement, Values, - WithClause, WithQuery, + WithClause, }; use inherent::inherent; @@ -51,6 +51,7 @@ pub struct InsertStatement { pub(crate) on_conflict: Option, pub(crate) returning: Option, pub(crate) default_values: Option, + pub(crate) with: Option, } impl InsertStatement { @@ -425,7 +426,7 @@ impl InsertStatement { self.returning(ReturningClause::All) } - /// Create a [WithQuery] by specifying a [WithClause] to execute this query with. + /// Specify a [WithClause] to execute this query with. /// /// # Examples /// @@ -448,13 +449,13 @@ impl InsertStatement { /// .columns([Glyph::Id, Glyph::Image, Glyph::Aspect]) /// .from(Alias::new("cte")) /// .to_owned(); - /// let mut insert = Query::insert(); - /// insert + /// let mut query = Query::insert(); + /// query /// .into_table(Glyph::Table) /// .columns([Glyph::Id, Glyph::Image, Glyph::Aspect]) /// .select_from(select) - /// .unwrap(); - /// let query = insert.with(with_clause); + /// .unwrap() + /// .with(with_clause); /// /// assert_eq!( /// query.to_string(MysqlQueryBuilder), @@ -469,8 +470,9 @@ impl InsertStatement { /// r#"WITH "cte" ("id", "image", "aspect") AS (SELECT "id", "image", "aspect" FROM "glyph") INSERT INTO "glyph" ("id", "image", "aspect") SELECT "id", "image", "aspect" FROM "cte""# /// ); /// ``` - pub fn with(self, clause: WithClause) -> WithQuery { - clause.query(self) + pub fn with(&mut self, clause: WithClause) -> &mut Self { + self.with = Some(clause); + self } /// Insert with default values if columns and values are not supplied. diff --git a/src/query/mod.rs b/src/query/mod.rs index eb414efee..eeb065aef 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -52,7 +52,6 @@ pub enum SubQueryStatement { InsertStatement(InsertStatement), UpdateStatement(UpdateStatement), DeleteStatement(DeleteStatement), - WithStatement(WithQuery), } impl Query { diff --git a/src/query/select.rs b/src/query/select.rs index debd0807b..19d0920f7 100644 --- a/src/query/select.rs +++ b/src/query/select.rs @@ -6,7 +6,7 @@ use crate::{ types::*, value::*, FunctionCall, QueryStatementBuilder, QueryStatementWriter, SubQueryStatement, WindowStatement, - WithClause, WithQuery, + WithClause, }; use inherent::inherent; @@ -56,6 +56,7 @@ pub struct SelectStatement { pub(crate) window: Option<(DynIden, WindowStatement)>, #[cfg(feature = "backend-mysql")] pub(crate) index_hints: Vec, + pub(crate) with: Option, } /// List of distinct keywords that can be used in select statement @@ -164,6 +165,7 @@ impl SelectStatement { window: self.window.take(), #[cfg(feature = "backend-mysql")] index_hints: std::mem::take(&mut self.index_hints), + with: self.with.take(), } } @@ -2280,7 +2282,7 @@ impl SelectStatement { self } - /// Create a [WithQuery] by specifying a [WithClause] to execute this query with. + /// Specify a [WithClause] to execute this query with. /// /// # Examples /// @@ -2316,18 +2318,16 @@ impl SelectStatement { /// .table_name(Alias::new("cte_traversal")) /// .to_owned(); /// - /// let select = SelectStatement::new() - /// .column(ColumnRef::Asterisk) - /// .from(Alias::new("cte_traversal")) - /// .to_owned(); - /// /// let with_clause = WithClause::new() /// .recursive(true) /// .cte(common_table_expression) /// .cycle(Cycle::new_from_expr_set_using(SimpleExpr::Column(ColumnRef::Column(Alias::new("id").into_iden())), Alias::new("looped"), Alias::new("traversal_path"))) /// .to_owned(); /// - /// let query = select.with(with_clause).to_owned(); + /// let query = SelectStatement::new() + /// .column(ColumnRef::Asterisk) + /// .from(Alias::new("cte_traversal")) + /// .with(with_clause).to_owned(); /// /// assert_eq!( /// query.to_string(MysqlQueryBuilder), @@ -2342,8 +2342,9 @@ impl SelectStatement { /// r#"WITH RECURSIVE "cte_traversal" ("id", "depth", "next", "value") AS (SELECT "id", 1, "next", "value" FROM "table" UNION ALL SELECT "id", "depth" + 1, "next", "value" FROM "table" INNER JOIN "cte_traversal" ON "cte_traversal"."next" = "table"."id") SELECT * FROM "cte_traversal""# /// ); /// ``` - pub fn with(self, clause: WithClause) -> WithQuery { - clause.query(self) + pub fn with(&mut self, clause: WithClause) -> &mut Self { + self.with = Some(clause); + self } /// WINDOW diff --git a/src/query/update.rs b/src/query/update.rs index b267e813f..e1dbb2ecb 100644 --- a/src/query/update.rs +++ b/src/query/update.rs @@ -6,7 +6,6 @@ use crate::{ types::*, value::*, QueryStatementBuilder, QueryStatementWriter, ReturningClause, SubQueryStatement, WithClause, - WithQuery, }; use inherent::inherent; @@ -44,6 +43,7 @@ pub struct UpdateStatement { pub(crate) orders: Vec, pub(crate) limit: Option, pub(crate) returning: Option, + pub(crate) with: Option, } impl UpdateStatement { @@ -247,7 +247,7 @@ impl UpdateStatement { self.returning(ReturningClause::All) } - /// Create a [WithQuery] by specifying a [WithClause] to execute this query with. + /// Specify a [WithClause] to execute this query with. /// /// # Examples /// @@ -265,12 +265,12 @@ impl UpdateStatement { /// .table_name(Alias::new("cte")) /// .to_owned(); /// let with_clause = WithClause::new().cte(cte).to_owned(); - /// let update = UpdateStatement::new() + /// let query = UpdateStatement::new() /// .table(Glyph::Table) /// .and_where(Expr::col(Glyph::Id).in_subquery(SelectStatement::new().column(Glyph::Id).from(Alias::new("cte")).to_owned())) /// .value(Glyph::Aspect, Expr::cust("60 * 24 * 24")) + /// .with(with_clause) /// .to_owned(); - /// let query = update.with(with_clause); /// /// assert_eq!( /// query.to_string(MysqlQueryBuilder), @@ -285,8 +285,9 @@ impl UpdateStatement { /// r#"WITH "cte" ("id") AS (SELECT "id" FROM "glyph" WHERE "image" LIKE '0%') UPDATE "glyph" SET "aspect" = 60 * 24 * 24 WHERE "id" IN (SELECT "id" FROM "cte")"# /// ); /// ``` - pub fn with(self, clause: WithClause) -> WithQuery { - clause.query(self) + pub fn with(&mut self, clause: WithClause) -> &mut Self { + self.with = Some(clause); + self } /// Get column values diff --git a/src/query/with.rs b/src/query/with.rs index 94933f912..24d90b4ff 100644 --- a/src/query/with.rs +++ b/src/query/with.rs @@ -400,18 +400,16 @@ impl Cycle { /// .table_name(Alias::new("cte_traversal")) /// .to_owned(); /// -/// let select = SelectStatement::new() -/// .column(ColumnRef::Asterisk) -/// .from(Alias::new("cte_traversal")) -/// .to_owned(); -/// /// let with_clause = WithClause::new() /// .recursive(true) /// .cte(common_table_expression) /// .cycle(Cycle::new_from_expr_set_using(SimpleExpr::Column(ColumnRef::Column(Alias::new("id").into_iden())), Alias::new("looped"), Alias::new("traversal_path"))) /// .to_owned(); /// -/// let query = select.with(with_clause).to_owned(); +/// let query = SelectStatement::new() +/// .column(ColumnRef::Asterisk) +/// .from(Alias::new("cte_traversal")) +/// .with(with_clause).to_owned(); /// /// assert_eq!( /// query.to_string(MysqlQueryBuilder), @@ -475,130 +473,4 @@ impl WithClause { self.cte_expressions.push(cte); self } - - /// You can turn this into a [WithQuery] using this function. The resulting WITH query will - /// execute the argument query with this WITH clause. - pub fn query(self, query: T) -> WithQuery - where - T: QueryStatementBuilder + 'static, - { - WithQuery::new().with_clause(self).query(query).to_owned() - } -} -/// A WITH query. A simple SQL query that has a WITH clause ([WithClause]). -/// -/// The [WithClause] can contain one or multiple common table expressions ([CommonTableExpression]). -/// -/// These named queries can act as a "query local table" that are materialized during execution and -/// then can be used by the query prefixed with the WITH clause. -/// -/// A WITH clause can contain multiple of these [CommonTableExpression]. (Except in the case of -/// recursive WITH query which can only contain one [CommonTableExpression]). -/// -/// A [CommonTableExpression] is a name, column names and a query returning data for those columns. -/// -/// Some databases (like sqlite) restrict the acceptable kinds of queries inside of the WITH clause -/// common table expressions. These databases only allow [SelectStatement]s to form a common table -/// expression. -/// -/// Other databases like postgres allow modification queries (UPDATE, DELETE) inside of the WITH -/// clause but they have to return a table. (They must have a RETURNING clause). -/// -/// sea-query doesn't check this or restrict the kind of [CommonTableExpression] that you can create -/// in rust. This means that you can put an UPDATE or DELETE queries into WITH clause and sea-query -/// will succeed in generating that kind of sql query but the execution inside the database will -/// fail because they are invalid. -/// -/// It is your responsibility to ensure that the kind of WITH clause that you put together makes -/// sense and valid for that database that you are using. -/// -/// NOTE that for recursive WITH queries (in sql: "WITH RECURSIVE") you can only have a -/// single [CommonTableExpression] inside of the WITH clause. That query must match certain -/// requirements: -/// * It is a query of UNION or UNION ALL of two queries. -/// * The first part of the query (the left side of the UNION) must be executable first in itself. -/// It must be non-recursive. (Cannot contain self reference) -/// * The self reference must appear in the right hand side of the UNION. -/// * The query can only have a single self-reference. -/// * Recursive data-modifying statements are not supported, but you can use the results of a -/// recursive SELECT query in a data-modifying statement. (like so: WITH RECURSIVE -/// cte_name(a,b,c,d) AS (SELECT ... UNION SELECT ... FROM ... JOIN cte_name ON ... WHERE ...) -/// DELETE FROM table WHERE table.a = cte_name.a) -/// -/// It is mandatory to set the [Self::cte] and the [Self::query]. -#[derive(Debug, Clone, Default, PartialEq)] -pub struct WithQuery { - pub(crate) with_clause: WithClause, - pub(crate) query: Option>, -} - -impl WithQuery { - /// Constructs a new empty [WithQuery]. - pub fn new() -> Self { - Self::default() - } - - /// Set the whole [WithClause]. - pub fn with_clause(&mut self, with_clause: WithClause) -> &mut Self { - self.with_clause = with_clause; - self - } - - /// Set the [WithClause::recursive]. See that method for more information. - pub fn recursive(&mut self, recursive: bool) -> &mut Self { - self.with_clause.recursive = recursive; - self - } - - /// Add the [WithClause::search]. See that method for more information. - pub fn search(&mut self, search: Search) -> &mut Self { - self.with_clause.search = Some(search); - self - } - - /// Set the [WithClause::cycle]. See that method for more information. - pub fn cycle(&mut self, cycle: Cycle) -> &mut Self { - self.with_clause.cycle = Some(cycle); - self - } - - /// Add a [CommonTableExpression] to the with clause. See [WithClause::cte]. - pub fn cte(&mut self, cte: CommonTableExpression) -> &mut Self { - self.with_clause.cte_expressions.push(cte); - self - } - - /// Set the query that you execute with the [WithClause]. - pub fn query(&mut self, query: T) -> &mut Self - where - T: QueryStatementBuilder, - { - self.query = Some(Box::new(query.into_sub_query_statement())); - self - } -} - -impl QueryStatementBuilder for WithQuery { - fn build_collect_any_into(&self, query_builder: &dyn QueryBuilder, sql: &mut dyn SqlWriter) { - query_builder.prepare_with_query(self, sql); - } - - fn into_sub_query_statement(self) -> SubQueryStatement { - SubQueryStatement::WithStatement(self) - } -} - -#[inherent] -impl QueryStatementWriter for WithQuery { - pub fn build_collect_into(&self, query_builder: T, sql: &mut dyn SqlWriter) { - query_builder.prepare_with_query(self, sql); - } - - pub fn build_collect( - &self, - query_builder: T, - sql: &mut dyn SqlWriter, - ) -> String; - pub fn build(&self, query_builder: T) -> (String, Values); - pub fn to_string(&self, query_builder: T) -> String; } diff --git a/tests/postgres/query.rs b/tests/postgres/query.rs index cf88dc968..a88b3c914 100644 --- a/tests/postgres/query.rs +++ b/tests/postgres/query.rs @@ -994,12 +994,12 @@ fn select_58() { .table_name(Alias::new("cte")) .to_owned(); let with_clause = WithClause::new().cte(cte).to_owned(); - let select = SelectStatement::new() - .columns([Glyph::Id, Glyph::Image, Glyph::Aspect]) - .from(Alias::new("cte")) - .to_owned(); assert_eq!( - select.with(with_clause).to_string(PostgresQueryBuilder), + SelectStatement::new() + .columns([Glyph::Id, Glyph::Image, Glyph::Aspect]) + .from(Alias::new("cte")) + .with(with_clause) + .to_string(PostgresQueryBuilder), [ r#"WITH "cte" AS"#, r#"(SELECT "id", "image", "aspect""#, @@ -1076,12 +1076,12 @@ fn select_62() { .table_name(Alias::new("cte")) .to_owned(); let with_clause = WithClause::new().cte(cte).to_owned(); - let select = SelectStatement::new() - .columns([Alias::new("column1"), Alias::new("column2")]) - .from(Alias::new("cte")) - .to_owned(); assert_eq!( - select.with(with_clause).to_string(PostgresQueryBuilder), + SelectStatement::new() + .columns([Alias::new("column1"), Alias::new("column2")]) + .from(Alias::new("cte")) + .with(with_clause) + .to_string(PostgresQueryBuilder), [ r#"WITH "cte" AS"#, r#"(SELECT * FROM (VALUES (1, 'hello'), (2, 'world')) AS "x")"#,