diff --git a/CHANGELOG.md b/CHANGELOG.md index fe5a02367b..950903baa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,9 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/ * Added support for the PostgreSQL `ALL` operator. See http://docs.diesel.rs/diesel/pg/expression/dsl/fn.all.html for details. +* Added support for `RETURNING` expressions in `DELETE` statements. Implicitly + these queries will use `RETURNING *`. + ### Changed * Diesel now targets `nightly-2016-07-07`. Future releases will update to a diff --git a/diesel/src/macros/mod.rs b/diesel/src/macros/mod.rs index a2001c3423..09f28cc4c6 100644 --- a/diesel/src/macros/mod.rs +++ b/diesel/src/macros/mod.rs @@ -45,6 +45,13 @@ macro_rules! column { { } + impl SelectableExpression< + $crate::query_source::filter::FilteredQuerySource, ST> + for $column_name where + $column_name: SelectableExpression + { + } + impl $crate::expression::NonAggregate for $column_name {} impl $crate::query_source::Column for $column_name { diff --git a/diesel/src/query_builder/delete_statement.rs b/diesel/src/query_builder/delete_statement.rs index b297f05ee6..e64d916460 100644 --- a/diesel/src/query_builder/delete_statement.rs +++ b/diesel/src/query_builder/delete_statement.rs @@ -1,4 +1,5 @@ -use backend::Backend; +use backend::{Backend, SupportsReturningClause}; +use expression::{Expression, SelectableExpression, NonAggregate}; use query_builder::*; use query_source::Table; use result::QueryResult; @@ -44,3 +45,99 @@ impl QueryFragment for DeleteStatement where } impl_query_id!(noop: DeleteStatement); + +impl AsQuery for DeleteStatement where + T: Table, + ::AllColumns: Expression + SelectableExpression, + DeleteQuery<::AllColumns, DeleteStatement>: Query, +{ + type SqlType = ::SqlType; + type Query = DeleteQuery<::AllColumns, DeleteStatement>; + + fn as_query(self) -> Self::Query { + DeleteQuery { + returning: T::all_columns(), + statement: self, + } + } +} + +impl DeleteStatement { + /// Specify what expression is returned after execution of the `delete`. + /// + /// # Examples + /// + /// ### Deleting a record: + /// + /// ```rust + /// # #[macro_use] extern crate diesel; + /// # include!("src/doctest_setup.rs"); + /// # + /// # table! { + /// # users { + /// # id -> Integer, + /// # name -> VarChar, + /// # } + /// # } + /// # + /// # #[cfg(feature = "postgres")] + /// # fn main() { + /// # use self::users::dsl::*; + /// # let connection = establish_connection(); + /// let deleted_name = diesel::delete(users.filter(name.eq("Sean"))) + /// .returning(name) + /// .get_result(&connection); + /// assert_eq!(Ok("Sean".to_string()), deleted_name); + /// # } + /// # #[cfg(not(feature = "postgres"))] + /// # fn main() {} + /// ``` + pub fn returning(self, returns: E) -> DeleteQuery where + E: Expression + SelectableExpression, + DeleteQuery: Query, + { + DeleteQuery { + returning: returns, + statement: self, + } + } +} + +#[doc(hidden)] +#[derive(Debug, Copy, Clone)] +pub struct DeleteQuery { + returning: T, + statement: U, +} + +impl Query for DeleteQuery where + T: Expression + NonAggregate, +{ + type SqlType = T::SqlType; +} + +impl QueryFragment for DeleteQuery where + DB: Backend + SupportsReturningClause, + T: QueryFragment, + U: QueryFragment, +{ + fn to_sql(&self, out: &mut DB::QueryBuilder) -> BuildQueryResult { + try!(self.statement.to_sql(out)); + out.push_sql(" RETURNING "); + try!(self.returning.to_sql(out)); + Ok(()) + } + + fn collect_binds(&self, out: &mut DB::BindCollector) -> QueryResult<()> { + try!(self.statement.collect_binds(out)); + try!(self.returning.collect_binds(out)); + Ok(()) + } + + fn is_safe_to_cache_prepared(&self) -> bool { + self.statement.is_safe_to_cache_prepared() && + self.returning.is_safe_to_cache_prepared() + } +} + +impl_query_id!(noop: DeleteQuery); diff --git a/diesel_compile_tests/tests/compile-fail/delete_statement_does_not_support_returning_methods_on_sqlite.rs b/diesel_compile_tests/tests/compile-fail/delete_statement_does_not_support_returning_methods_on_sqlite.rs new file mode 100644 index 0000000000..4b75765a7b --- /dev/null +++ b/diesel_compile_tests/tests/compile-fail/delete_statement_does_not_support_returning_methods_on_sqlite.rs @@ -0,0 +1,26 @@ +#[macro_use] +extern crate diesel; + +use diesel::*; +use diesel::sqlite::SqliteConnection; + +table! { + users { + id -> Integer, + name -> VarChar, + } +} + +fn main() { + use self::users::dsl::*; + let connection = SqliteConnection::establish(":memory:").unwrap(); + + delete(users.filter(name.eq("Bill"))) + .get_result(&connection); + //~^ ERROR SupportsReturningClause + + delete(users.filter(name.eq("Bill"))) + .returning(name) + .get_result(&connection); + //~^ ERROR SupportsReturningClause +} diff --git a/diesel_compile_tests/tests/compile-fail/returning_clause_requires_selectable_expression.rs b/diesel_compile_tests/tests/compile-fail/returning_clause_requires_selectable_expression.rs new file mode 100644 index 0000000000..bb6c90935e --- /dev/null +++ b/diesel_compile_tests/tests/compile-fail/returning_clause_requires_selectable_expression.rs @@ -0,0 +1,44 @@ +#[macro_use] +extern crate diesel; + +use diesel::*; +use diesel::pg::PgConnection; + +table! { + users { + id -> Integer, + name -> VarChar, + } +} + +pub struct NewUser(String); + +Insertable! { + (users) + pub struct NewUser(#[column_name(name)] String,); +} + +table! { + non_users { + id -> Integer, + noname -> VarChar, + } +} + +fn main() { + let connection = PgConnection::establish("").unwrap(); + + delete(users::table.filter(users::columns::name.eq("Bill"))) + .returning(non_users::columns::noname); + //~^ ERROR SelectableExpression + + insert(&NewUser("Hello".into())) + .into(users::table) + .returning(non_users::columns::noname); + //~^ ERROR SelectableExpression + + update(users::table) + .set(users::columns::name.eq("Bill")) + .returning(non_users::columns::noname); + //~^ ERROR SelectableExpression +} diff --git a/diesel_tests/tests/delete.rs b/diesel_tests/tests/delete.rs index 4adbd6e534..bf3b6661c1 100644 --- a/diesel_tests/tests/delete.rs +++ b/diesel_tests/tests/delete.rs @@ -27,3 +27,18 @@ fn delete_single_record() { assert_eq!(Ok(vec![tess]), users.load(&connection)); } + +#[test] +#[cfg(not(feature = "sqlite"))] +fn return_deleted_records() { + use schema::users::dsl::*; + let connection = connection_with_sean_and_tess_in_users_table(); + + let deleted_name = delete(users.filter(name.eq("Sean"))) + .returning(name) + .get_result(&connection); + assert_eq!(Ok("Sean".to_string()), deleted_name); + + let num_users = users.count().first(&connection); + assert_eq!(Ok(1), num_users); +}