Skip to content

Commit

Permalink
Add a Selectable trait
Browse files Browse the repository at this point in the history
This commit adds a `Selectable` trait, a corresponding derive and some
methods that allow to construct the select / returning clause based on a
type implementing this trait before sending the corresponding query to
the database.

This trait is designed to be used in combination with `Queryable` which
represents a result of a query. Ultimativly my hope here is that this
combination drastically reduces the number of type missmatches that
occure because of missmatches between query and struct implementing `Queryable`.
  • Loading branch information
weiznich committed Apr 8, 2021
1 parent a11dae0 commit 84f0593
Show file tree
Hide file tree
Showing 15 changed files with 476 additions and 5 deletions.
2 changes: 2 additions & 0 deletions diesel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ pub mod prelude {
#[doc(inline)]
pub use crate::query_builder::DecoratableTarget;
#[doc(inline)]
pub use crate::query_builder::Selectable;
#[doc(inline)]
pub use crate::query_dsl::{
BelongingToDsl, CombineDsl, JoinOnDsl, QueryDsl, RunQueryDsl, SaveChangesDsl,
};
Expand Down
14 changes: 13 additions & 1 deletion diesel/src/query_builder/delete_statement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use crate::expression::{AppearsOnTable, SelectableExpression};
use crate::query_builder::returning_clause::*;
use crate::query_builder::where_clause::*;
use crate::query_builder::*;
use crate::query_dsl::load_dsl::LoadIntoDsl;
use crate::query_dsl::methods::{BoxedDsl, FilterDsl};
use crate::query_dsl::RunQueryDsl;
use crate::query_dsl::{LoadQuery, RunQueryDsl};
use crate::query_source::Table;
use crate::result::QueryResult;

Expand Down Expand Up @@ -231,3 +232,14 @@ impl<T, U> DeleteStatement<T, U, NoReturningClause> {
}
}
}

impl<T, U, Conn, S> LoadIntoDsl<Conn, S> for DeleteStatement<T, U, NoReturningClause>
where
S: Selectable,
S::SelectExpression: SelectableExpression<T>,
DeleteStatement<T, U, ReturningClause<S::SelectExpression>>: Query + LoadQuery<Conn, S>,
{
fn load_into(self, conn: &Conn) -> QueryResult<Vec<S>> {
self.returning(S::selection()).internal_load(conn)
}
}
15 changes: 14 additions & 1 deletion diesel/src/query_builder/insert_statement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::fmt::{self, Debug, Display};
use std::marker::PhantomData;

use super::returning_clause::*;
use super::select_clause::Selectable;
use crate::backend::Backend;
use crate::expression::grouped::Grouped;
use crate::expression::operators::Eq;
Expand All @@ -18,9 +19,10 @@ use crate::insertable::*;
#[cfg(feature = "mysql")]
use crate::mysql::Mysql;
use crate::query_builder::*;
use crate::query_dsl::load_dsl::LoadIntoDsl;
#[cfg(feature = "sqlite")]
use crate::query_dsl::methods::ExecuteDsl;
use crate::query_dsl::RunQueryDsl;
use crate::query_dsl::{LoadQuery, RunQueryDsl};
use crate::query_source::{Column, Table};
use crate::result::QueryResult;
#[cfg(feature = "sqlite")]
Expand Down Expand Up @@ -204,6 +206,17 @@ where
}
}

impl<T, U, Op, Conn, S> LoadIntoDsl<Conn, S> for InsertStatement<T, U, Op>
where
S: Selectable,
S::SelectExpression: SelectableExpression<T>,
InsertStatement<T, U, Op, ReturningClause<S::SelectExpression>>: Query + LoadQuery<Conn, S>,
{
fn load_into(self, conn: &Conn) -> QueryResult<Vec<S>> {
self.returning(S::selection()).internal_load(conn)
}
}

#[cfg(feature = "sqlite")]
impl<'a, T, U, Op, C> ExecuteDsl<C, Sqlite> for InsertStatement<T, BatchInsert<'a, U, T>, Op>
where
Expand Down
2 changes: 1 addition & 1 deletion diesel/src/query_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub use self::insert_statement::{
pub use self::query_id::QueryId;
#[doc(inline)]
pub use self::select_clause::{
IntoBoxedSelectClause, SelectClauseExpression, SelectClauseQueryFragment,
IntoBoxedSelectClause, SelectClauseExpression, SelectClauseQueryFragment, Selectable,
};
#[doc(hidden)]
pub use self::select_statement::{BoxedSelectStatement, SelectStatement};
Expand Down
16 changes: 16 additions & 0 deletions diesel/src/query_builder/select_clause.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ pub struct DefaultSelectClause;
#[derive(Debug, Clone, Copy, QueryId)]
pub struct SelectClause<T>(pub T);

/// This trait represents a type that can be used to construct a
/// select or returning clause via
/// [`RunQueryDsl::load_into`](crate::query_dsl::RunQueryDsl::load_into)
///
/// This trait [can be derived](derive@Selectable)
pub trait Selectable {
/// The type of the select expression
type SelectExpression: Expression;

/// Construct the select expression
fn selection() -> Self::SelectExpression;
}

#[doc(inline)]
pub use diesel_derives::Selectable;

/// Specialised variant of `Expression` for select clause types
///
/// The difference to the normal `Expression` trait is the query source (`QS`)
Expand Down
12 changes: 12 additions & 0 deletions diesel/src/query_builder/select_statement/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::query_builder::offset_clause::OffsetClause;
use crate::query_builder::order_clause::OrderClause;
use crate::query_builder::where_clause::*;
use crate::query_builder::*;
use crate::query_dsl::load_dsl::LoadIntoDsl;
use crate::query_dsl::methods::*;
use crate::query_dsl::*;
use crate::query_source::joins::*;
Expand Down Expand Up @@ -401,6 +402,17 @@ where
}
}

impl<'a, Conn, U, ST, QS, DB, GB> LoadIntoDsl<Conn, U> for BoxedSelectStatement<'a, ST, QS, DB, GB>
where
U: Selectable,
Self: SelectDsl<U::SelectExpression>,
crate::dsl::Select<Self, U::SelectExpression>: LoadQuery<Conn, U>,
{
fn load_into(self, conn: &Conn) -> crate::QueryResult<Vec<U>> {
<_ as SelectDsl<_>>::select(self, U::selection()).internal_load(conn)
}
}

#[cfg(test)]
mod tests {
use crate::prelude::*;
Expand Down
13 changes: 13 additions & 0 deletions diesel/src/query_builder/select_statement/dsl_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::query_builder::{
AsQuery, IntoBoxedClause, Query, QueryFragment, SelectQuery, SelectStatement,
};
use crate::query_dsl::boxed_dsl::BoxedDsl;
use crate::query_dsl::load_dsl::LoadIntoDsl;
use crate::query_dsl::methods::*;
use crate::query_dsl::order_dsl::ValidOrderingForDistinct;
use crate::query_dsl::*;
Expand Down Expand Up @@ -536,3 +537,15 @@ where
CombinationClause::new(Except, All, self, rhs.as_query())
}
}

impl<Conn, U, F, S, D, W, O, LOf, G, LC> LoadIntoDsl<Conn, U>
for SelectStatement<F, S, D, W, O, LOf, G, LC>
where
U: Selectable,
Self: SelectDsl<U::SelectExpression>,
crate::dsl::Select<Self, U::SelectExpression>: LoadQuery<Conn, U>,
{
fn load_into(self, conn: &Conn) -> crate::QueryResult<Vec<U>> {
<_ as SelectDsl<_>>::select(self, U::selection()).internal_load(conn)
}
}
15 changes: 14 additions & 1 deletion diesel/src/query_builder/update_statement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ use crate::expression::{
use crate::query_builder::returning_clause::*;
use crate::query_builder::where_clause::*;
use crate::query_builder::*;
use crate::query_dsl::load_dsl::LoadIntoDsl;
use crate::query_dsl::methods::{BoxedDsl, FilterDsl};
use crate::query_dsl::RunQueryDsl;
use crate::query_dsl::{LoadQuery, RunQueryDsl};
use crate::query_source::Table;
use crate::result::Error::QueryBuilderError;
use crate::result::QueryResult;
Expand Down Expand Up @@ -286,3 +287,15 @@ impl<T, U, V> UpdateStatement<T, U, V, NoReturningClause> {
/// Indicates that you have not yet called `.set` on an update statement
#[derive(Debug, Clone, Copy)]
pub struct SetNotCalled;

impl<T, U, V, Conn, S> LoadIntoDsl<Conn, S> for UpdateStatement<T, U, V>
where
S: Selectable,
T: Table,
S::SelectExpression: SelectableExpression<T>,
UpdateStatement<T, U, V, ReturningClause<S::SelectExpression>>: Query + LoadQuery<Conn, S>,
{
fn load_into(self, conn: &Conn) -> QueryResult<Vec<S>> {
self.returning(S::selection()).internal_load(conn)
}
}
15 changes: 15 additions & 0 deletions diesel/src/query_dsl/load_dsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::deserialize::FromSqlRow;
use crate::expression::QueryMetadata;
use crate::query_builder::{AsQuery, QueryFragment, QueryId};
use crate::result::QueryResult;
use crate::Table;

/// The `load` method
///
Expand All @@ -18,6 +19,10 @@ pub trait LoadQuery<Conn, U>: RunQueryDsl<Conn> {
fn internal_load(self, conn: &Conn) -> QueryResult<Vec<U>>;
}

pub trait LoadIntoDsl<Conn, U>: RunQueryDsl<Conn> {
fn load_into(self, conn: &Conn) -> QueryResult<Vec<U>>;
}

impl<Conn, T, U> LoadQuery<Conn, U> for T
where
Conn: Connection,
Expand All @@ -31,6 +36,16 @@ where
}
}

impl<Conn, T, U> LoadIntoDsl<Conn, U> for T
where
T: Table + AsQuery,
T::Query: LoadIntoDsl<Conn, U>,
{
fn load_into(self, conn: &Conn) -> QueryResult<Vec<U>> {
<_ as LoadIntoDsl<Conn, U>>::load_into(self.as_query(), conn)
}
}

/// The `execute` method
///
/// This trait should not be relied on directly by most apps. Its behavior is
Expand Down
117 changes: 117 additions & 0 deletions diesel/src/query_dsl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mod single_value_dsl;
pub use self::belonging_to_dsl::BelongingToDsl;
pub use self::combine_dsl::CombineDsl;
pub use self::join_dsl::{InternalJoinDsl, JoinOnDsl, JoinWithImplicitOnClause};
use self::load_dsl::LoadIntoDsl;
#[doc(hidden)]
pub use self::load_dsl::LoadQuery;
pub use self::save_changes_dsl::{SaveChangesDsl, UpdateAndFetchResults};
Expand Down Expand Up @@ -1303,8 +1304,63 @@ pub trait RunQueryDsl<Conn>: Sized {
self.internal_load(conn)
}

/// Executes the given query, returning a `Vec` with the returned rows.
///
/// This method is similar to [load](self::RunQueryDsl::load), but sets
/// a corresponding select/returning clause based on the
/// [Selectable](crate::query_builder::Selectable) impl for `U`.
///
///
/// # Examples
///
/// ## Returning a struct
///
/// ```rust
/// # include!("../doctest_setup.rs");
/// # use schema::users;
/// #
/// #[derive(Selectable, Queryable, PartialEq, Debug)]
/// #[table_name = "users"]
/// struct User {
/// id: i32,
/// name: String,
/// }
///
/// # fn main() {
/// # run_test();
/// # }
/// #
/// # fn run_test() -> QueryResult<()> {
/// # use diesel::insert_into;
/// # use schema::users::dsl::*;
/// # let connection = establish_connection();
/// let data = users
/// .load_into::<User>(&connection)?;
///
/// // This query is equivalent to
/// // users.select((users::id, users::name)).load::<User>(&connection)?;
///
/// let expected_data = vec![
/// User { id: 1, name: String::from("Sean") },
/// User { id: 2, name: String::from("Tess") },
/// ];
/// assert_eq!(expected_data, data);
/// # Ok(())
/// # }
/// ```
fn load_into<U>(self, conn: &Conn) -> QueryResult<Vec<U>>
where
Self: LoadIntoDsl<Conn, U>,
{
LoadIntoDsl::load_into(self, conn)
}

/// Runs the command, and returns the affected row.
///
/// This method behaves similar to [get_result](self::RunQueryDsl::get_result)
/// than [load_into](self::RunQueryDsl::load_into) behaves to
/// [load](self::RunQueryDsl::load)
///
/// `Err(NotFound)` will be returned if the query affected 0 rows. You can
/// call `.optional()` on the result of this if the command was optional to
/// get back a `Result<Option<U>>`
Expand Down Expand Up @@ -1355,6 +1411,67 @@ pub trait RunQueryDsl<Conn>: Sized {
first_or_not_found(self.load(conn))
}

/// Runs the command, and returns the affected row.
///
/// `Err(NotFound)` will be returned if the query affected 0 rows. You can
/// call `.optional()` on the result of this if the command was optional to
/// get back a `Result<Option<U>>`
///
/// When this method is called on an insert, update, or delete statement,
/// it will implicitly add a `RETURNING *` to the query,
/// unless a returning clause was already specified.
///
/// This method only returns the first row that was affected, even if more
/// rows are affected.
///
/// # Example
///
/// ```rust
/// # include!("../doctest_setup.rs");
/// #
/// #
/// # fn main() {
/// # run_test();
/// # }
/// #
/// # use schema::users;
/// #
/// #[derive(Selectable, Queryable, PartialEq, Debug)]
/// #[table_name = "users"]
/// pub struct UserName {
/// name: String
/// }
/// #
/// # #[cfg(feature = "postgres")]
/// # fn run_test() -> QueryResult<()> {
/// # use diesel::{insert_into, update};
/// # use schema::users::dsl::*;
/// # let connection = establish_connection();
/// let inserted_row = insert_into(users)
/// .values(name.eq("Ruby"))
/// .load_into::<UserName>(&connection)?;
/// assert_eq!(UserName { name: "Ruby".into_string() }, inserted_row);
///
/// // This will return `NotFound`, as there is no user with ID 4
/// let update_result = update(users.find(4))
/// .set(name.eq("Jim"))
/// .load_into::<UserName>(&connection);
/// assert_eq!(Err(diesel::NotFound), update_result);
/// # Ok(())
/// # }
/// #
/// # #[cfg(not(feature = "postgres"))]
/// # fn run_test() -> QueryResult<()> {
/// # Ok(())
/// # }
/// ```
fn load_into_single<U>(self, conn: &Conn) -> QueryResult<U>
where
Self: LoadIntoDsl<Conn, U>,
{
first_or_not_found(<_ as RunQueryDsl<_>>::load_into(self, conn))
}

/// Runs the command, returning an `Vec` with the affected rows.
///
/// This method is an alias for [`load`], but with a name that makes more
Expand Down
Loading

0 comments on commit 84f0593

Please sign in to comment.