Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Selectable trait #2709

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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`.
///
Comment on lines +1310 to +1313
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should also explain what the difference between Queryable and Selectable and when you may want to use each of them.

Also you can mention what bind method will be used: by name or by index

///
/// # 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)
Comment on lines +1363 to +1365
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// This method behaves similar to [get_result](self::RunQueryDsl::get_result)
/// than [load_into](self::RunQueryDsl::load_into) behaves to
/// [load](self::RunQueryDsl::load)
/// This method's behavior is more 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