Skip to content

Commit

Permalink
Allow to specialize QueryFragment impls easily
Browse files Browse the repository at this point in the history
The main motivation of this change is to allow third party backends like
diesel-oci to speciaize `QueryFragment` impls without using the unstable
`specialization` feature. This is done by introducing a new marker trait
`SqlDialect` that has an associated type for each specializable
`QueryFragment` impl.

Beside of that this allows us to fix a few long standing issues:
* `eq_any` can now use `= ANY()` on postgres
* We can now support batch inserts for sqlite as long as there is no
default value in the insert values

Fixes #2898
Fixes #2694 (by deprecating the corresponding functions)
  • Loading branch information
weiznich committed Oct 5, 2021
1 parent a4a70d9 commit fa3dc3e
Show file tree
Hide file tree
Showing 65 changed files with 1,483 additions and 441 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,12 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
* The `#[table_name]` attribute for derive macros can now refer to any path and is no
longer limited to identifiers from the current scope.

* Interacting with a database requires a mutable connection.
* Interacting with a database requires a mutable connection.

* `eq_any()` now emits a `= ANY()` expression for the postgresql backend instead of `IN()`
* `ne_all()` now emits a `!= ALL()` expression for the postgresql backend instead of `NOT IN()`
* The sqlite backend now uses a single batch insert statement if there are now default values present
in the values clause

### Fixed

Expand Down Expand Up @@ -230,6 +235,9 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/

* `diesel::pg::upsert` has been deprecated to support upsert queries on more than one backend.
Please use `diesel::upsert` instead.

* `diesel::dsl::any` and `diesel::dsl::all` are now deprecated in
favour of `ExpressionMethods::eq_any()` and `ExpressionMethods::ne_all()`

### Upgrade Notes

Expand Down
213 changes: 197 additions & 16 deletions diesel/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::sql_types::{self, HasSqlType};
/// protocol used to communicated with the database.
pub trait Backend
where
Self: Sized,
Self: Sized + SqlDialect,
Self: HasSqlType<sql_types::SmallInt>,
Self: HasSqlType<sql_types::Integer>,
Self: HasSqlType<sql_types::BigInt>,
Expand Down Expand Up @@ -73,19 +73,200 @@ pub trait BinaryRawValue<'a>: HasRawValue<'a> {
/// `FromSql`. Equivalent to `<DB as Backend>::RawValue<'a>`.
pub type RawValue<'a, DB> = <DB as HasRawValue<'a>>::RawValue;

/// A trait indicating that implementing backend provides support for
/// `RETURNING` clauses.
/// This trait provides various options to configure the
/// generated SQL for a specific backend.
///
/// This trait has to be implemented in order to be able to use methods such as
/// `get_results` and `returning`. Namely, any method which leads to usage of
/// `RETURNING` clauses in SQL sent to backend will require that this trait
/// be implemented for used backend.
pub trait SupportsReturningClause {}
/// Does this backend support 'ON CONFLICT' clause?
pub trait SupportsOnConflictClause {}
/// Does this backend support 'WHERE' clauses on 'ON CONFLICT' clauses?
pub trait SupportsOnConflictTargetDecorations {}
/// Does this backend support the bare `DEFAULT` keyword?
pub trait SupportsDefaultKeyword {}
/// Does this backend use the standard `SAVEPOINT` syntax?
pub trait UsesAnsiSavepointSyntax {}
/// Accessing anything from this trait is considered to be part of the
/// public API. Implementing this trait is not considered to be part of
/// diesels public API, as future versions of diesel may add additional
/// associated constants here.
///
/// Each associated type is used to configure the behaviour
/// of one or more [`QueryFragment`](crate::query_builder::QueryFragment)
/// implementations by providing
/// a custom `QueryFragment<YourBackend, YourSpecialSyntaxType>` implementation
/// to specialize on generic `QueryFragment<DB, DB::AssociatedType>` implementations.
///
/// See the [`sql_dialect`] module for options provided by diesel out of the box.
pub trait SqlDialect {
/// Configures how this backends supports `RETURNING` clauses
///
/// This allows backends to opt in `RETURNING` clause support and to
/// provide a custom [`QueryFragment`](crate::query_builder::QueryFragment)
/// implementation for [`ReturningClause`](crate::query_builder::ReturningClause)
type ReturningClause;
/// Configures how this backend supports `ON CONFLICT` clauses
///
/// This allows backends to opt in `ON CONFLICT` clause support
type OnConflictClause;
/// Configures how this backend handles the bare `DEFAULT` keyword for
/// inserting the default value in a `INSERT INTO` `VALUES` clause
///
/// This allows backends to opt in support for `DEFAULT` value expressions
/// for insert statements
type InsertWithDefaultKeyword;
/// Configures how this backend handles Batch insert statements
///
/// This allows backends to provide a custom [`QueryFragment`](crate::query_builder::QueryFragment)
/// implementation for [`BatchInsert`](crate::query_builder::BatchInsert)
type BatchInsertSupport;
/// Configures how this backend handles the `DEFAULT VALUES` clause for
/// insert statements.
///
/// This allows backends to provide a custom [`QueryFragment`](crate::query_builder::QueryFragment)
/// implementation for [`DefaultValues`](crate::query_builder::DefaultValues)
type DefaultValueClauseForInsert;
/// Configures how this backend handles empty `FROM` clauses for select statements.
///
/// This allows backends to provide a custom [`QueryFragment`](crate::query_builder::QueryFragment)
/// implementation for [`NoFromClause`](crate::query_builder::NoFromClause)
type EmptyFromClauseSyntax;
/// Configures how this backend handles `EXISTS()` expressions.
///
/// This allows backends to provide a custom [`QueryFragment`](crate::query_builder::QueryFragment)
/// implementation for [`Exists`](crate::expression::exists::Exists)
type ExistsSyntax;

/// Configures how this backend handles `IN()` and `NOT IN()` expressions.
///
/// This allows backends to provide custom [`QueryFragment`](crate::query_builder::QueryFragment)
/// implementations for [`In`](crate::expression::array_comparison::In),
/// [`NotIn`](crate::expression::array_comparison::NotIn) and
/// [`Many`](crate::expression::array_comparison::Many)
type ArrayComparision;
}

/// This module contains all options provided by diesel to configure the [`SqlDialect`] trait.
pub mod sql_dialect {
#[cfg(doc)]
use super::SqlDialect;

/// This module contains all diesel provided reusable options to
/// configure [`SqlDialect::OnConflictClause`]
pub mod on_conflict_clause {
/// A marker trait indicating if a `ON CONFLICT` clause is supported or not
///
/// If you use a custom type to specify specialized support for `ON CONFLICT` clauses
/// implementing this trait opts into reusing diesels existing `ON CONFLICT`
/// `QueryFragment` implementations
pub trait SupportsOnConflictClause {}

/// This marker type indicates that `ON CONFLICT` clauses are not supported for this backend
#[derive(Debug, Copy, Clone)]
pub struct DoesNotSupportOnConflictClause;
}

/// This module contains all reusable options to configure
/// [`SqlDialect::ReturningClause`]
pub mod returning_clause {
/// A marker trait indicating if a `RETURNING` clause is supported or not
///
/// If you use custom type to specify specialized support for `RETURNING` clauses
/// implementing this trait opts in supporting `RETURNING` clause syntax
pub trait SupportsReturningClause {}

/// Indicates that a backend provides support for `RETURNING` clauses
/// using the postgresql `RETURNING` syntax
#[derive(Debug, Copy, Clone)]
pub struct PgLikeReturningClause;

/// Indicates that a backend does not support `RETURNING` clauses
#[derive(Debug, Copy, Clone)]
pub struct DoesNotSupportReturningClause;

impl SupportsReturningClause for PgLikeReturningClause {}
}

/// This module contains all reusable options to configure
/// [`SqlDialect::InsertWithDefaultKeyword`]
pub mod default_keyword_for_insert {
/// A marker trait indicating if a `DEFAULT` like expression
/// is supported as part of `INSERT INTO` clauses to indicate
/// that a default value should be inserted at a specific position
///
/// If you use a custom type to specify specialized support for `DEFAULT`
/// expressions implementing this trait opts in support for `DEFAULT`
/// value expressions for inserts. Otherwise diesel will emulate this
/// behaviour.
pub trait SupportsDefaultKeyword {}

/// Indicates that a backend support `DEFAULT` value expressions
/// for `INSERT INTO` statements based on the ISO SQL standard
#[derive(Debug, Copy, Clone)]
pub struct IsoSqlDefaultKeyword;

/// Indicates that a backend does not support `DEFAULT` value
/// expressions0for `INSERT INTO` statements
#[derive(Debug, Copy, Clone)]
pub struct DoesNotSupportDefaultKeyword;

impl SupportsDefaultKeyword for IsoSqlDefaultKeyword {}
}

/// This module contains all reusable options to configure
/// [`SqlDialect::BatchInsertSupport`]
pub mod batch_insert_support {
/// A marker trait indicating if batch insert statements
/// are supported for this backend or not
pub trait SupportsBatchInsert {}

/// Indicates that this backend does not support batch
/// insert statements.
/// In this case diesel will emulate batch insert support
/// by inserting each row on it's own
#[derive(Debug, Copy, Clone)]
pub struct DoesNotSupportBatchInsert;

/// Indicates that this backend supports postgres style
/// batch insert statements to insert multiple rows using one
/// insert statement
#[derive(Debug, Copy, Clone)]
pub struct PostgresLikeBatchInsertSupport;

impl SupportsBatchInsert for PostgresLikeBatchInsertSupport {}
}

/// This module contains all reusable options to configure
/// [`SqlDialect::DefaultValueClauseForInsert`]
pub mod default_value_clause {

/// Indicates that this backend uses the
/// `DEFAULT VALUES` syntax to specify
/// that a row consisting only of default
/// values should be inserted
#[derive(Debug, Clone, Copy)]
pub struct AnsiDefaultValueClause;
}

/// This module contains all reusable options to configure
/// [`SqlDialect::EmptyFromClauseSyntax`]
pub mod from_clause_syntax {

/// Indicates that this backend skips
/// the `FROM` clause in `SELECT` statements
/// if no table/view is queried
#[derive(Debug, Copy, Clone)]
pub struct AnsiSqlFromClauseSyntax;
}

/// This module contains all reusable options to configure
/// [`SqlDialect::ExistsSyntax`]
pub mod exists_syntax {

/// Indicates that this backend
/// treats `EXIST()` as function
/// like expression
#[derive(Debug, Copy, Clone)]
pub struct AnsiSqlExistsSyntax;
}

/// This module contains all reusable options to configure
/// [`SqlDialect::ArrayComparision`]
pub mod array_comparision {

/// Indicates that this backend requires a single bind
/// per array element in `IN()` and `NOT IN()` expression
#[derive(Debug, Copy, Clone)]
pub struct AnsiSqlArrayComparison;
}
}
3 changes: 0 additions & 3 deletions diesel/src/connection/transaction_manager.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::backend::UsesAnsiSavepointSyntax;
use crate::connection::Connection;
use crate::result::{DatabaseErrorKind, Error, QueryResult};

Expand Down Expand Up @@ -65,7 +64,6 @@ impl AnsiTransactionManager {
pub fn begin_transaction_sql<Conn>(conn: &mut Conn, sql: &str) -> QueryResult<()>
where
Conn: Connection<TransactionManager = Self>,
Conn::Backend: UsesAnsiSavepointSyntax,
{
use crate::result::Error::AlreadyInTransaction;

Expand All @@ -81,7 +79,6 @@ impl AnsiTransactionManager {
impl<Conn> TransactionManager<Conn> for AnsiTransactionManager
where
Conn: Connection<TransactionManager = Self>,
Conn::Backend: UsesAnsiSavepointSyntax,
{
type TransactionStateData = Self;

Expand Down

0 comments on commit fa3dc3e

Please sign in to comment.