Skip to content

Commit

Permalink
Merge pull request #2741 from weiznich/feature/selectable_v2
Browse files Browse the repository at this point in the history
Feature/selectable v2
  • Loading branch information
weiznich authored Apr 27, 2021
2 parents 4dfb934 + bf9e2a8 commit bada4ad
Show file tree
Hide file tree
Showing 33 changed files with 1,767 additions and 54 deletions.
6 changes: 4 additions & 2 deletions diesel/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::backend::Backend;
use crate::deserialize::FromSqlRow;
use crate::expression::QueryMetadata;
use crate::query_builder::{AsQuery, QueryFragment, QueryId};
use crate::query_dsl::load_dsl::CompatibleType;
use crate::result::*;

#[doc(hidden)]
Expand Down Expand Up @@ -180,12 +181,13 @@ pub trait Connection: SimpleConnection + Send {
fn execute(&self, query: &str) -> QueryResult<usize>;

#[doc(hidden)]
fn load<T, U>(&self, source: T) -> QueryResult<Vec<U>>
fn load<T, U, ST>(&self, source: T) -> QueryResult<Vec<U>>
where
Self: Sized,
T: AsQuery,
T::Query: QueryFragment<Self::Backend> + QueryId,
U: FromSqlRow<T::SqlType, Self::Backend>,
T::SqlType: CompatibleType<U, Self::Backend, SqlType = ST>,
U: FromSqlRow<ST, Self::Backend>,
Self::Backend: QueryMetadata<T::SqlType>;

#[doc(hidden)]
Expand Down
17 changes: 15 additions & 2 deletions diesel/src/deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ use std::error::Error;
use std::result;

use crate::backend::{self, Backend};
use crate::expression::select_by::SelectBy;
use crate::row::{NamedRow, Row};
use crate::sql_types::{SingleValue, SqlType, Untyped};
use crate::Selectable;

/// A specialized result type representing the result of deserializing
/// a value from the database.
Expand Down Expand Up @@ -385,10 +387,21 @@ pub trait FromStaticSqlRow<ST, DB: Backend>: Sized {
fn build_from_row<'a>(row: &impl Row<'a, DB>) -> Result<Self>;
}

#[doc(hidden)]
pub trait SqlTypeOrSelectable {}

impl<ST> SqlTypeOrSelectable for ST where ST: SqlType + SingleValue {}
impl<U, DB> SqlTypeOrSelectable for SelectBy<U, DB>
where
U: Selectable<DB>,
DB: Backend,
{
}

impl<T, ST, DB> FromSqlRow<ST, DB> for T
where
T: Queryable<ST, DB>,
ST: SqlType,
ST: SqlTypeOrSelectable,
DB: Backend,
T::Row: FromStaticSqlRow<ST, DB>,
{
Expand Down Expand Up @@ -434,7 +447,7 @@ where

impl<T, ST, DB> StaticallySizedRow<ST, DB> for T
where
ST: SqlType + crate::util::TupleSize,
ST: SqlTypeOrSelectable + crate::util::TupleSize,
T: Queryable<ST, DB>,
DB: Backend,
{
Expand Down
122 changes: 122 additions & 0 deletions diesel/src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub mod nullable;
#[macro_use]
pub mod operators;
#[doc(hidden)]
pub mod select_by;
#[doc(hidden)]
pub mod sql_literal;
#[doc(hidden)]
pub mod subselect;
Expand Down Expand Up @@ -318,6 +320,126 @@ where
{
}

/// Trait indicating that a record can be selected and queried from the database.
///
/// Types which implement `Selectable` represent the select clause of a SQL query.
/// Use [`SelectableHelper::as_select()`] to construct the select clause. Once you
/// called `.select(YourType::as_select())` we enforce at the type system level that you
/// use the same type to load the query result into.
///
/// The constructed select clause can contain arbitrary expressions coming from different
/// tables. The corresponding [derive](derive@Selectable) provides a simple way to
/// construct a select clause matching fields to the corresponding table columns.
///
/// # Examples
///
/// If you just want to construct a select clause using an existing struct, you can use
/// `#[derive(Selectable)]`, See [`#[derive(Selectable)]`](derive@Selectable) for details.
///
///
/// ```rust
/// # include!("../doctest_setup.rs");
/// #
/// use schema::users;
///
/// #[derive(Queryable, PartialEq, Debug, Selectable)]
/// struct User {
/// id: i32,
/// name: String,
/// }
///
/// # fn main() {
/// # run_test();
/// # }
/// #
/// # fn run_test() -> QueryResult<()> {
/// # use schema::users::dsl::*;
/// # let connection = establish_connection();
/// let first_user = users.select(User::as_select()).first(&connection)?;
/// let expected = User { id: 1, name: "Sean".into() };
/// assert_eq!(expected, first_user);
/// # Ok(())
/// # }
/// ```
///
/// Alternatively, we can implement the trait for our struct manually.
///
/// ```rust
/// # include!("../doctest_setup.rs");
/// #
/// use schema::users;
/// use diesel::prelude::{Queryable, Selectable};
/// use diesel::backend::Backend;
///
/// #[derive(Queryable, PartialEq, Debug)]
/// struct User {
/// id: i32,
/// name: String,
/// }
///
/// impl<DB> Selectable<DB> for User
/// where
/// DB: Backend
/// {
/// type SelectExpression = (users::id, users::name);
///
/// fn construct_selection() -> Self::SelectExpression {
/// (users::id, users::name)
/// }
/// }
///
/// # fn main() {
/// # run_test();
/// # }
/// #
/// # fn run_test() -> QueryResult<()> {
/// # use schema::users::dsl::*;
/// # let connection = establish_connection();
/// let first_user = users.select(User::as_select()).first(&connection)?;
/// let expected = User { id: 1, name: "Sean".into() };
/// assert_eq!(expected, first_user);
/// # Ok(())
/// # }
/// ```
pub trait Selectable<DB: Backend> {
/// The expression you'd like to select.
///
/// This is typically a tuple of corresponding to the table columns of your struct's fields.
type SelectExpression: Expression;

/// Construct an instance of the expression
fn construct_selection() -> Self::SelectExpression;
}

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

/// This helper trait provides several methods for
/// constructing a select or returning clause based on a
/// [`Selectable`] implementation.
pub trait SelectableHelper<DB: Backend>: Selectable<DB> + Sized {
/// Construct a select clause based on a [`Selectable`] implementation.
///
/// The returned select clause enforces that you use the same type
/// for constructing the select clause and for loading the query result into.
fn as_select() -> select_by::SelectBy<Self, DB>;

/// An alias for `as_select` that can be used with returning clauses
fn as_returning() -> select_by::SelectBy<Self, DB> {
Self::as_select()
}
}

impl<T, DB> SelectableHelper<DB> for T
where
T: Selectable<DB>,
DB: Backend,
{
fn as_select() -> select_by::SelectBy<Self, DB> {
select_by::SelectBy::new()
}
}

/// Is this expression valid for a given group by clause?
///
/// Implementations of this trait must ensure that aggregate expressions are
Expand Down
13 changes: 13 additions & 0 deletions diesel/src/expression/nullable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ impl<T: QueryId> QueryId for Nullable<T> {
const HAS_STATIC_QUERY_ID: bool = T::HAS_STATIC_QUERY_ID;
}

impl<T, DB> Selectable<DB> for Option<T>
where
DB: Backend,
T: Selectable<DB>,
Nullable<T::SelectExpression>: Expression,
{
type SelectExpression = Nullable<T::SelectExpression>;

fn construct_selection() -> Self::SelectExpression {
Nullable::new(T::construct_selection())
}
}

impl<T, QS> SelectableExpression<QS> for Nullable<T>
where
Self: AppearsOnTable<QS>,
Expand Down
103 changes: 103 additions & 0 deletions diesel/src/expression/select_by.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use crate::backend::Backend;
use crate::dsl::SqlTypeOf;
use crate::expression::{
AppearsOnTable, Expression, QueryMetadata, Selectable, SelectableExpression,
TypedExpressionType, ValidGrouping,
};
use crate::query_builder::*;
use crate::result::QueryResult;

#[derive(Debug, Default)]
pub struct SelectBy<T, DB>(std::marker::PhantomData<(T, DB)>);

impl<T, DB> Clone for SelectBy<T, DB> {
fn clone(&self) -> Self {
Self(self.0)
}
}

impl<T, DB> Copy for SelectBy<T, DB> {}

impl<T, E, DB> QueryId for SelectBy<T, DB>
where
DB: Backend,
T: Selectable<DB, SelectExpression = E>,
E: QueryId + Expression,
{
type QueryId = E::QueryId;

const HAS_STATIC_QUERY_ID: bool = E::HAS_STATIC_QUERY_ID;
}

impl<T, DB> SelectBy<T, DB> {
pub(crate) fn new() -> Self {
Self(Default::default())
}
}

impl<T, E, DB> Expression for SelectBy<T, DB>
where
DB: Backend,
T: Selectable<DB, SelectExpression = E>,
E: QueryId + Expression,
{
type SqlType = SelectBy<T, DB>;
}

impl<T, DB> TypedExpressionType for SelectBy<T, DB>
where
T: Selectable<DB>,
DB: Backend,
{
}

impl<T, GB, E, DB> ValidGrouping<GB> for SelectBy<T, DB>
where
DB: Backend,
T: Selectable<DB, SelectExpression = E>,
E: Expression + ValidGrouping<GB>,
{
type IsAggregate = E::IsAggregate;
}

impl<T, DB> QueryMetadata<SelectBy<T, DB>> for DB
where
DB: Backend,
T: Selectable<DB>,
DB: QueryMetadata<SqlTypeOf<T::SelectExpression>>,
{
fn row_metadata(lookup: &Self::MetadataLookup, out: &mut Vec<Option<Self::TypeMetadata>>) {
<DB as QueryMetadata<SqlTypeOf<<T as Selectable<DB>>::SelectExpression>>>::row_metadata(
lookup, out,
)
}
}

impl<T, DB> QueryFragment<DB> for SelectBy<T, DB>
where
T: Selectable<DB>,
T::SelectExpression: QueryFragment<DB>,
DB: Backend,
{
fn walk_ast(&self, out: AstPass<DB>) -> QueryResult<()> {
T::construct_selection().walk_ast(out)
}
}

impl<T, QS, DB> SelectableExpression<QS> for SelectBy<T, DB>
where
DB: Backend,
T: Selectable<DB>,
T::SelectExpression: SelectableExpression<QS>,
Self: AppearsOnTable<QS>,
{
}

impl<T, QS, DB> AppearsOnTable<QS> for SelectBy<T, DB>
where
DB: Backend,
T: Selectable<DB>,
T::SelectExpression: AppearsOnTable<QS>,
Self: Expression,
{
}
4 changes: 3 additions & 1 deletion diesel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ pub mod prelude {
pub use crate::deserialize::{Queryable, QueryableByName};
#[doc(inline)]
pub use crate::expression::{
AppearsOnTable, BoxableExpression, Expression, IntoSql, SelectableExpression,
AppearsOnTable, BoxableExpression, Expression, IntoSql, Selectable, SelectableExpression,
};

#[doc(inline)]
Expand All @@ -407,6 +407,8 @@ pub mod prelude {
#[doc(inline)]
pub use crate::result::{ConnectionError, ConnectionResult, OptionalExtension, QueryResult};

pub use crate::expression::SelectableHelper;

#[cfg(feature = "mysql")]
#[doc(inline)]
pub use crate::mysql::MysqlConnection;
Expand Down
6 changes: 4 additions & 2 deletions diesel/src/mysql/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::deserialize::FromSqlRow;
use crate::expression::QueryMetadata;
use crate::query_builder::bind_collector::RawBytesBindCollector;
use crate::query_builder::*;
use crate::query_dsl::load_dsl::CompatibleType;
use crate::result::*;

#[allow(missing_debug_implementations, missing_copy_implementations)]
Expand Down Expand Up @@ -59,11 +60,12 @@ impl Connection for MysqlConnection {
}

#[doc(hidden)]
fn load<T, U>(&self, source: T) -> QueryResult<Vec<U>>
fn load<T, U, ST>(&self, source: T) -> QueryResult<Vec<U>>
where
T: AsQuery,
T::Query: QueryFragment<Self::Backend> + QueryId,
U: FromSqlRow<T::SqlType, Self::Backend>,
T::SqlType: CompatibleType<U, Self::Backend, SqlType = ST>,
U: FromSqlRow<ST, Self::Backend>,
Self::Backend: QueryMetadata<T::SqlType>,
{
use crate::result::Error::DeserializationError;
Expand Down
6 changes: 4 additions & 2 deletions diesel/src/pg/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::pg::metadata_lookup::{GetPgMetadataCache, PgMetadataCache};
use crate::pg::{Pg, TransactionBuilder};
use crate::query_builder::bind_collector::RawBytesBindCollector;
use crate::query_builder::*;
use crate::query_dsl::load_dsl::CompatibleType;
use crate::result::ConnectionError::CouldntSetupConfiguration;
use crate::result::Error::DeserializationError;
use crate::result::*;
Expand Down Expand Up @@ -68,11 +69,12 @@ impl Connection for PgConnection {
}

#[doc(hidden)]
fn load<T, U>(&self, source: T) -> QueryResult<Vec<U>>
fn load<T, U, ST>(&self, source: T) -> QueryResult<Vec<U>>
where
T: AsQuery,
T::Query: QueryFragment<Self::Backend> + QueryId,
U: FromSqlRow<T::SqlType, Self::Backend>,
T::SqlType: CompatibleType<U, Self::Backend, SqlType = ST>,
U: FromSqlRow<ST, Self::Backend>,
Self::Backend: QueryMetadata<T::SqlType>,
{
let (query, params) = self.prepare_query(&source.as_query())?;
Expand Down
Loading

0 comments on commit bada4ad

Please sign in to comment.