Implement RETURNING clause for DELETE statements

This commit is contained in:
Robert Maloney 2016-05-06 13:54:16 -07:00
parent 332ba12617
commit e48d8addce
6 changed files with 193 additions and 1 deletions

View File

@ -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

View File

@ -45,6 +45,13 @@ macro_rules! column {
{
}
impl<ST, Source, Predicate> SelectableExpression<
$crate::query_source::filter::FilteredQuerySource<Source, Predicate>, ST>
for $column_name where
$column_name: SelectableExpression<Source, ST>
{
}
impl $crate::expression::NonAggregate for $column_name {}
impl $crate::query_source::Column for $column_name {

View File

@ -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<T, U, DB> QueryFragment<DB> for DeleteStatement<T, U> where
}
impl_query_id!(noop: DeleteStatement<T, U>);
impl<T, U> AsQuery for DeleteStatement<T, U> where
T: Table,
<T as Table>::AllColumns: Expression + SelectableExpression<T>,
DeleteQuery<<T as Table>::AllColumns, DeleteStatement<T, U>>: Query,
{
type SqlType = <Self::Query as Query>::SqlType;
type Query = DeleteQuery<<T as Table>::AllColumns, DeleteStatement<T, U>>;
fn as_query(self) -> Self::Query {
DeleteQuery {
returning: T::all_columns(),
statement: self,
}
}
}
impl<T, U> DeleteStatement<T, U> {
/// 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<E>(self, returns: E) -> DeleteQuery<E, Self> where
E: Expression + SelectableExpression<T>,
DeleteQuery<E, Self>: Query,
{
DeleteQuery {
returning: returns,
statement: self,
}
}
}
#[doc(hidden)]
#[derive(Debug, Copy, Clone)]
pub struct DeleteQuery<T, U> {
returning: T,
statement: U,
}
impl<T, U> Query for DeleteQuery<T, U> where
T: Expression + NonAggregate,
{
type SqlType = T::SqlType;
}
impl<T, U, DB> QueryFragment<DB> for DeleteQuery<T, U> where
DB: Backend + SupportsReturningClause,
T: QueryFragment<DB>,
U: QueryFragment<DB>,
{
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<T, U>);

View File

@ -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
}

View File

@ -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
}

View File

@ -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);
}