Skip to content

Commit 84f0593

Browse files
committed
Add a Selectable trait
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`.
1 parent a11dae0 commit 84f0593

File tree

15 files changed

+476
-5
lines changed

15 files changed

+476
-5
lines changed

diesel/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ pub mod prelude {
399399
#[doc(inline)]
400400
pub use crate::query_builder::DecoratableTarget;
401401
#[doc(inline)]
402+
pub use crate::query_builder::Selectable;
403+
#[doc(inline)]
402404
pub use crate::query_dsl::{
403405
BelongingToDsl, CombineDsl, JoinOnDsl, QueryDsl, RunQueryDsl, SaveChangesDsl,
404406
};

diesel/src/query_builder/delete_statement/mod.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use crate::expression::{AppearsOnTable, SelectableExpression};
44
use crate::query_builder::returning_clause::*;
55
use crate::query_builder::where_clause::*;
66
use crate::query_builder::*;
7+
use crate::query_dsl::load_dsl::LoadIntoDsl;
78
use crate::query_dsl::methods::{BoxedDsl, FilterDsl};
8-
use crate::query_dsl::RunQueryDsl;
9+
use crate::query_dsl::{LoadQuery, RunQueryDsl};
910
use crate::query_source::Table;
1011
use crate::result::QueryResult;
1112

@@ -231,3 +232,14 @@ impl<T, U> DeleteStatement<T, U, NoReturningClause> {
231232
}
232233
}
233234
}
235+
236+
impl<T, U, Conn, S> LoadIntoDsl<Conn, S> for DeleteStatement<T, U, NoReturningClause>
237+
where
238+
S: Selectable,
239+
S::SelectExpression: SelectableExpression<T>,
240+
DeleteStatement<T, U, ReturningClause<S::SelectExpression>>: Query + LoadQuery<Conn, S>,
241+
{
242+
fn load_into(self, conn: &Conn) -> QueryResult<Vec<S>> {
243+
self.returning(S::selection()).internal_load(conn)
244+
}
245+
}

diesel/src/query_builder/insert_statement/mod.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::fmt::{self, Debug, Display};
1010
use std::marker::PhantomData;
1111

1212
use super::returning_clause::*;
13+
use super::select_clause::Selectable;
1314
use crate::backend::Backend;
1415
use crate::expression::grouped::Grouped;
1516
use crate::expression::operators::Eq;
@@ -18,9 +19,10 @@ use crate::insertable::*;
1819
#[cfg(feature = "mysql")]
1920
use crate::mysql::Mysql;
2021
use crate::query_builder::*;
22+
use crate::query_dsl::load_dsl::LoadIntoDsl;
2123
#[cfg(feature = "sqlite")]
2224
use crate::query_dsl::methods::ExecuteDsl;
23-
use crate::query_dsl::RunQueryDsl;
25+
use crate::query_dsl::{LoadQuery, RunQueryDsl};
2426
use crate::query_source::{Column, Table};
2527
use crate::result::QueryResult;
2628
#[cfg(feature = "sqlite")]
@@ -204,6 +206,17 @@ where
204206
}
205207
}
206208

209+
impl<T, U, Op, Conn, S> LoadIntoDsl<Conn, S> for InsertStatement<T, U, Op>
210+
where
211+
S: Selectable,
212+
S::SelectExpression: SelectableExpression<T>,
213+
InsertStatement<T, U, Op, ReturningClause<S::SelectExpression>>: Query + LoadQuery<Conn, S>,
214+
{
215+
fn load_into(self, conn: &Conn) -> QueryResult<Vec<S>> {
216+
self.returning(S::selection()).internal_load(conn)
217+
}
218+
}
219+
207220
#[cfg(feature = "sqlite")]
208221
impl<'a, T, U, Op, C> ExecuteDsl<C, Sqlite> for InsertStatement<T, BatchInsert<'a, U, T>, Op>
209222
where

diesel/src/query_builder/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub use self::insert_statement::{
4545
pub use self::query_id::QueryId;
4646
#[doc(inline)]
4747
pub use self::select_clause::{
48-
IntoBoxedSelectClause, SelectClauseExpression, SelectClauseQueryFragment,
48+
IntoBoxedSelectClause, SelectClauseExpression, SelectClauseQueryFragment, Selectable,
4949
};
5050
#[doc(hidden)]
5151
pub use self::select_statement::{BoxedSelectStatement, SelectStatement};

diesel/src/query_builder/select_clause.rs

+16
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ pub struct DefaultSelectClause;
88
#[derive(Debug, Clone, Copy, QueryId)]
99
pub struct SelectClause<T>(pub T);
1010

11+
/// This trait represents a type that can be used to construct a
12+
/// select or returning clause via
13+
/// [`RunQueryDsl::load_into`](crate::query_dsl::RunQueryDsl::load_into)
14+
///
15+
/// This trait [can be derived](derive@Selectable)
16+
pub trait Selectable {
17+
/// The type of the select expression
18+
type SelectExpression: Expression;
19+
20+
/// Construct the select expression
21+
fn selection() -> Self::SelectExpression;
22+
}
23+
24+
#[doc(inline)]
25+
pub use diesel_derives::Selectable;
26+
1127
/// Specialised variant of `Expression` for select clause types
1228
///
1329
/// The difference to the normal `Expression` trait is the query source (`QS`)

diesel/src/query_builder/select_statement/boxed.rs

+12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::query_builder::offset_clause::OffsetClause;
1515
use crate::query_builder::order_clause::OrderClause;
1616
use crate::query_builder::where_clause::*;
1717
use crate::query_builder::*;
18+
use crate::query_dsl::load_dsl::LoadIntoDsl;
1819
use crate::query_dsl::methods::*;
1920
use crate::query_dsl::*;
2021
use crate::query_source::joins::*;
@@ -401,6 +402,17 @@ where
401402
}
402403
}
403404

405+
impl<'a, Conn, U, ST, QS, DB, GB> LoadIntoDsl<Conn, U> for BoxedSelectStatement<'a, ST, QS, DB, GB>
406+
where
407+
U: Selectable,
408+
Self: SelectDsl<U::SelectExpression>,
409+
crate::dsl::Select<Self, U::SelectExpression>: LoadQuery<Conn, U>,
410+
{
411+
fn load_into(self, conn: &Conn) -> crate::QueryResult<Vec<U>> {
412+
<_ as SelectDsl<_>>::select(self, U::selection()).internal_load(conn)
413+
}
414+
}
415+
404416
#[cfg(test)]
405417
mod tests {
406418
use crate::prelude::*;

diesel/src/query_builder/select_statement/dsl_impls.rs

+13
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::query_builder::{
2121
AsQuery, IntoBoxedClause, Query, QueryFragment, SelectQuery, SelectStatement,
2222
};
2323
use crate::query_dsl::boxed_dsl::BoxedDsl;
24+
use crate::query_dsl::load_dsl::LoadIntoDsl;
2425
use crate::query_dsl::methods::*;
2526
use crate::query_dsl::order_dsl::ValidOrderingForDistinct;
2627
use crate::query_dsl::*;
@@ -536,3 +537,15 @@ where
536537
CombinationClause::new(Except, All, self, rhs.as_query())
537538
}
538539
}
540+
541+
impl<Conn, U, F, S, D, W, O, LOf, G, LC> LoadIntoDsl<Conn, U>
542+
for SelectStatement<F, S, D, W, O, LOf, G, LC>
543+
where
544+
U: Selectable,
545+
Self: SelectDsl<U::SelectExpression>,
546+
crate::dsl::Select<Self, U::SelectExpression>: LoadQuery<Conn, U>,
547+
{
548+
fn load_into(self, conn: &Conn) -> crate::QueryResult<Vec<U>> {
549+
<_ as SelectDsl<_>>::select(self, U::selection()).internal_load(conn)
550+
}
551+
}

diesel/src/query_builder/update_statement/mod.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ use crate::expression::{
1212
use crate::query_builder::returning_clause::*;
1313
use crate::query_builder::where_clause::*;
1414
use crate::query_builder::*;
15+
use crate::query_dsl::load_dsl::LoadIntoDsl;
1516
use crate::query_dsl::methods::{BoxedDsl, FilterDsl};
16-
use crate::query_dsl::RunQueryDsl;
17+
use crate::query_dsl::{LoadQuery, RunQueryDsl};
1718
use crate::query_source::Table;
1819
use crate::result::Error::QueryBuilderError;
1920
use crate::result::QueryResult;
@@ -286,3 +287,15 @@ impl<T, U, V> UpdateStatement<T, U, V, NoReturningClause> {
286287
/// Indicates that you have not yet called `.set` on an update statement
287288
#[derive(Debug, Clone, Copy)]
288289
pub struct SetNotCalled;
290+
291+
impl<T, U, V, Conn, S> LoadIntoDsl<Conn, S> for UpdateStatement<T, U, V>
292+
where
293+
S: Selectable,
294+
T: Table,
295+
S::SelectExpression: SelectableExpression<T>,
296+
UpdateStatement<T, U, V, ReturningClause<S::SelectExpression>>: Query + LoadQuery<Conn, S>,
297+
{
298+
fn load_into(self, conn: &Conn) -> QueryResult<Vec<S>> {
299+
self.returning(S::selection()).internal_load(conn)
300+
}
301+
}

diesel/src/query_dsl/load_dsl.rs

+15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::deserialize::FromSqlRow;
55
use crate::expression::QueryMetadata;
66
use crate::query_builder::{AsQuery, QueryFragment, QueryId};
77
use crate::result::QueryResult;
8+
use crate::Table;
89

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

22+
pub trait LoadIntoDsl<Conn, U>: RunQueryDsl<Conn> {
23+
fn load_into(self, conn: &Conn) -> QueryResult<Vec<U>>;
24+
}
25+
2126
impl<Conn, T, U> LoadQuery<Conn, U> for T
2227
where
2328
Conn: Connection,
@@ -31,6 +36,16 @@ where
3136
}
3237
}
3338

39+
impl<Conn, T, U> LoadIntoDsl<Conn, U> for T
40+
where
41+
T: Table + AsQuery,
42+
T::Query: LoadIntoDsl<Conn, U>,
43+
{
44+
fn load_into(self, conn: &Conn) -> QueryResult<Vec<U>> {
45+
<_ as LoadIntoDsl<Conn, U>>::load_into(self.as_query(), conn)
46+
}
47+
}
48+
3449
/// The `execute` method
3550
///
3651
/// This trait should not be relied on directly by most apps. Its behavior is

diesel/src/query_dsl/mod.rs

+117
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ mod single_value_dsl;
4747
pub use self::belonging_to_dsl::BelongingToDsl;
4848
pub use self::combine_dsl::CombineDsl;
4949
pub use self::join_dsl::{InternalJoinDsl, JoinOnDsl, JoinWithImplicitOnClause};
50+
use self::load_dsl::LoadIntoDsl;
5051
#[doc(hidden)]
5152
pub use self::load_dsl::LoadQuery;
5253
pub use self::save_changes_dsl::{SaveChangesDsl, UpdateAndFetchResults};
@@ -1303,8 +1304,63 @@ pub trait RunQueryDsl<Conn>: Sized {
13031304
self.internal_load(conn)
13041305
}
13051306

1307+
/// Executes the given query, returning a `Vec` with the returned rows.
1308+
///
1309+
/// This method is similar to [load](self::RunQueryDsl::load), but sets
1310+
/// a corresponding select/returning clause based on the
1311+
/// [Selectable](crate::query_builder::Selectable) impl for `U`.
1312+
///
1313+
///
1314+
/// # Examples
1315+
///
1316+
/// ## Returning a struct
1317+
///
1318+
/// ```rust
1319+
/// # include!("../doctest_setup.rs");
1320+
/// # use schema::users;
1321+
/// #
1322+
/// #[derive(Selectable, Queryable, PartialEq, Debug)]
1323+
/// #[table_name = "users"]
1324+
/// struct User {
1325+
/// id: i32,
1326+
/// name: String,
1327+
/// }
1328+
///
1329+
/// # fn main() {
1330+
/// # run_test();
1331+
/// # }
1332+
/// #
1333+
/// # fn run_test() -> QueryResult<()> {
1334+
/// # use diesel::insert_into;
1335+
/// # use schema::users::dsl::*;
1336+
/// # let connection = establish_connection();
1337+
/// let data = users
1338+
/// .load_into::<User>(&connection)?;
1339+
///
1340+
/// // This query is equivalent to
1341+
/// // users.select((users::id, users::name)).load::<User>(&connection)?;
1342+
///
1343+
/// let expected_data = vec![
1344+
/// User { id: 1, name: String::from("Sean") },
1345+
/// User { id: 2, name: String::from("Tess") },
1346+
/// ];
1347+
/// assert_eq!(expected_data, data);
1348+
/// # Ok(())
1349+
/// # }
1350+
/// ```
1351+
fn load_into<U>(self, conn: &Conn) -> QueryResult<Vec<U>>
1352+
where
1353+
Self: LoadIntoDsl<Conn, U>,
1354+
{
1355+
LoadIntoDsl::load_into(self, conn)
1356+
}
1357+
13061358
/// Runs the command, and returns the affected row.
13071359
///
1360+
/// This method behaves similar to [get_result](self::RunQueryDsl::get_result)
1361+
/// than [load_into](self::RunQueryDsl::load_into) behaves to
1362+
/// [load](self::RunQueryDsl::load)
1363+
///
13081364
/// `Err(NotFound)` will be returned if the query affected 0 rows. You can
13091365
/// call `.optional()` on the result of this if the command was optional to
13101366
/// get back a `Result<Option<U>>`
@@ -1355,6 +1411,67 @@ pub trait RunQueryDsl<Conn>: Sized {
13551411
first_or_not_found(self.load(conn))
13561412
}
13571413

1414+
/// Runs the command, and returns the affected row.
1415+
///
1416+
/// `Err(NotFound)` will be returned if the query affected 0 rows. You can
1417+
/// call `.optional()` on the result of this if the command was optional to
1418+
/// get back a `Result<Option<U>>`
1419+
///
1420+
/// When this method is called on an insert, update, or delete statement,
1421+
/// it will implicitly add a `RETURNING *` to the query,
1422+
/// unless a returning clause was already specified.
1423+
///
1424+
/// This method only returns the first row that was affected, even if more
1425+
/// rows are affected.
1426+
///
1427+
/// # Example
1428+
///
1429+
/// ```rust
1430+
/// # include!("../doctest_setup.rs");
1431+
/// #
1432+
/// #
1433+
/// # fn main() {
1434+
/// # run_test();
1435+
/// # }
1436+
/// #
1437+
/// # use schema::users;
1438+
/// #
1439+
/// #[derive(Selectable, Queryable, PartialEq, Debug)]
1440+
/// #[table_name = "users"]
1441+
/// pub struct UserName {
1442+
/// name: String
1443+
/// }
1444+
/// #
1445+
/// # #[cfg(feature = "postgres")]
1446+
/// # fn run_test() -> QueryResult<()> {
1447+
/// # use diesel::{insert_into, update};
1448+
/// # use schema::users::dsl::*;
1449+
/// # let connection = establish_connection();
1450+
/// let inserted_row = insert_into(users)
1451+
/// .values(name.eq("Ruby"))
1452+
/// .load_into::<UserName>(&connection)?;
1453+
/// assert_eq!(UserName { name: "Ruby".into_string() }, inserted_row);
1454+
///
1455+
/// // This will return `NotFound`, as there is no user with ID 4
1456+
/// let update_result = update(users.find(4))
1457+
/// .set(name.eq("Jim"))
1458+
/// .load_into::<UserName>(&connection);
1459+
/// assert_eq!(Err(diesel::NotFound), update_result);
1460+
/// # Ok(())
1461+
/// # }
1462+
/// #
1463+
/// # #[cfg(not(feature = "postgres"))]
1464+
/// # fn run_test() -> QueryResult<()> {
1465+
/// # Ok(())
1466+
/// # }
1467+
/// ```
1468+
fn load_into_single<U>(self, conn: &Conn) -> QueryResult<U>
1469+
where
1470+
Self: LoadIntoDsl<Conn, U>,
1471+
{
1472+
first_or_not_found(<_ as RunQueryDsl<_>>::load_into(self, conn))
1473+
}
1474+
13581475
/// Runs the command, returning an `Vec` with the affected rows.
13591476
///
13601477
/// This method is an alias for [`load`], but with a name that makes more

0 commit comments

Comments
 (0)