Skip to content

Commit fa3dc3e

Browse files
committed
Allow to specialize QueryFragment impls easily
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 diesel-rs#2898 Fixes diesel-rs#2694 (by deprecating the corresponding functions)
1 parent a4a70d9 commit fa3dc3e

65 files changed

Lines changed: 1483 additions & 441 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,12 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
175175
* The `#[table_name]` attribute for derive macros can now refer to any path and is no
176176
longer limited to identifiers from the current scope.
177177

178-
* Interacting with a database requires a mutable connection.
178+
* Interacting with a database requires a mutable connection.
179+
180+
* `eq_any()` now emits a `= ANY()` expression for the postgresql backend instead of `IN()`
181+
* `ne_all()` now emits a `!= ALL()` expression for the postgresql backend instead of `NOT IN()`
182+
* The sqlite backend now uses a single batch insert statement if there are now default values present
183+
in the values clause
179184

180185
### Fixed
181186

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

231236
* `diesel::pg::upsert` has been deprecated to support upsert queries on more than one backend.
232237
Please use `diesel::upsert` instead.
238+
239+
* `diesel::dsl::any` and `diesel::dsl::all` are now deprecated in
240+
favour of `ExpressionMethods::eq_any()` and `ExpressionMethods::ne_all()`
233241

234242
### Upgrade Notes
235243

diesel/src/backend.rs

Lines changed: 197 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::sql_types::{self, HasSqlType};
2020
/// protocol used to communicated with the database.
2121
pub trait Backend
2222
where
23-
Self: Sized,
23+
Self: Sized + SqlDialect,
2424
Self: HasSqlType<sql_types::SmallInt>,
2525
Self: HasSqlType<sql_types::Integer>,
2626
Self: HasSqlType<sql_types::BigInt>,
@@ -73,19 +73,200 @@ pub trait BinaryRawValue<'a>: HasRawValue<'a> {
7373
/// `FromSql`. Equivalent to `<DB as Backend>::RawValue<'a>`.
7474
pub type RawValue<'a, DB> = <DB as HasRawValue<'a>>::RawValue;
7575

76-
/// A trait indicating that implementing backend provides support for
77-
/// `RETURNING` clauses.
76+
/// This trait provides various options to configure the
77+
/// generated SQL for a specific backend.
7878
///
79-
/// This trait has to be implemented in order to be able to use methods such as
80-
/// `get_results` and `returning`. Namely, any method which leads to usage of
81-
/// `RETURNING` clauses in SQL sent to backend will require that this trait
82-
/// be implemented for used backend.
83-
pub trait SupportsReturningClause {}
84-
/// Does this backend support 'ON CONFLICT' clause?
85-
pub trait SupportsOnConflictClause {}
86-
/// Does this backend support 'WHERE' clauses on 'ON CONFLICT' clauses?
87-
pub trait SupportsOnConflictTargetDecorations {}
88-
/// Does this backend support the bare `DEFAULT` keyword?
89-
pub trait SupportsDefaultKeyword {}
90-
/// Does this backend use the standard `SAVEPOINT` syntax?
91-
pub trait UsesAnsiSavepointSyntax {}
79+
/// Accessing anything from this trait is considered to be part of the
80+
/// public API. Implementing this trait is not considered to be part of
81+
/// diesels public API, as future versions of diesel may add additional
82+
/// associated constants here.
83+
///
84+
/// Each associated type is used to configure the behaviour
85+
/// of one or more [`QueryFragment`](crate::query_builder::QueryFragment)
86+
/// implementations by providing
87+
/// a custom `QueryFragment<YourBackend, YourSpecialSyntaxType>` implementation
88+
/// to specialize on generic `QueryFragment<DB, DB::AssociatedType>` implementations.
89+
///
90+
/// See the [`sql_dialect`] module for options provided by diesel out of the box.
91+
pub trait SqlDialect {
92+
/// Configures how this backends supports `RETURNING` clauses
93+
///
94+
/// This allows backends to opt in `RETURNING` clause support and to
95+
/// provide a custom [`QueryFragment`](crate::query_builder::QueryFragment)
96+
/// implementation for [`ReturningClause`](crate::query_builder::ReturningClause)
97+
type ReturningClause;
98+
/// Configures how this backend supports `ON CONFLICT` clauses
99+
///
100+
/// This allows backends to opt in `ON CONFLICT` clause support
101+
type OnConflictClause;
102+
/// Configures how this backend handles the bare `DEFAULT` keyword for
103+
/// inserting the default value in a `INSERT INTO` `VALUES` clause
104+
///
105+
/// This allows backends to opt in support for `DEFAULT` value expressions
106+
/// for insert statements
107+
type InsertWithDefaultKeyword;
108+
/// Configures how this backend handles Batch insert statements
109+
///
110+
/// This allows backends to provide a custom [`QueryFragment`](crate::query_builder::QueryFragment)
111+
/// implementation for [`BatchInsert`](crate::query_builder::BatchInsert)
112+
type BatchInsertSupport;
113+
/// Configures how this backend handles the `DEFAULT VALUES` clause for
114+
/// insert statements.
115+
///
116+
/// This allows backends to provide a custom [`QueryFragment`](crate::query_builder::QueryFragment)
117+
/// implementation for [`DefaultValues`](crate::query_builder::DefaultValues)
118+
type DefaultValueClauseForInsert;
119+
/// Configures how this backend handles empty `FROM` clauses for select statements.
120+
///
121+
/// This allows backends to provide a custom [`QueryFragment`](crate::query_builder::QueryFragment)
122+
/// implementation for [`NoFromClause`](crate::query_builder::NoFromClause)
123+
type EmptyFromClauseSyntax;
124+
/// Configures how this backend handles `EXISTS()` expressions.
125+
///
126+
/// This allows backends to provide a custom [`QueryFragment`](crate::query_builder::QueryFragment)
127+
/// implementation for [`Exists`](crate::expression::exists::Exists)
128+
type ExistsSyntax;
129+
130+
/// Configures how this backend handles `IN()` and `NOT IN()` expressions.
131+
///
132+
/// This allows backends to provide custom [`QueryFragment`](crate::query_builder::QueryFragment)
133+
/// implementations for [`In`](crate::expression::array_comparison::In),
134+
/// [`NotIn`](crate::expression::array_comparison::NotIn) and
135+
/// [`Many`](crate::expression::array_comparison::Many)
136+
type ArrayComparision;
137+
}
138+
139+
/// This module contains all options provided by diesel to configure the [`SqlDialect`] trait.
140+
pub mod sql_dialect {
141+
#[cfg(doc)]
142+
use super::SqlDialect;
143+
144+
/// This module contains all diesel provided reusable options to
145+
/// configure [`SqlDialect::OnConflictClause`]
146+
pub mod on_conflict_clause {
147+
/// A marker trait indicating if a `ON CONFLICT` clause is supported or not
148+
///
149+
/// If you use a custom type to specify specialized support for `ON CONFLICT` clauses
150+
/// implementing this trait opts into reusing diesels existing `ON CONFLICT`
151+
/// `QueryFragment` implementations
152+
pub trait SupportsOnConflictClause {}
153+
154+
/// This marker type indicates that `ON CONFLICT` clauses are not supported for this backend
155+
#[derive(Debug, Copy, Clone)]
156+
pub struct DoesNotSupportOnConflictClause;
157+
}
158+
159+
/// This module contains all reusable options to configure
160+
/// [`SqlDialect::ReturningClause`]
161+
pub mod returning_clause {
162+
/// A marker trait indicating if a `RETURNING` clause is supported or not
163+
///
164+
/// If you use custom type to specify specialized support for `RETURNING` clauses
165+
/// implementing this trait opts in supporting `RETURNING` clause syntax
166+
pub trait SupportsReturningClause {}
167+
168+
/// Indicates that a backend provides support for `RETURNING` clauses
169+
/// using the postgresql `RETURNING` syntax
170+
#[derive(Debug, Copy, Clone)]
171+
pub struct PgLikeReturningClause;
172+
173+
/// Indicates that a backend does not support `RETURNING` clauses
174+
#[derive(Debug, Copy, Clone)]
175+
pub struct DoesNotSupportReturningClause;
176+
177+
impl SupportsReturningClause for PgLikeReturningClause {}
178+
}
179+
180+
/// This module contains all reusable options to configure
181+
/// [`SqlDialect::InsertWithDefaultKeyword`]
182+
pub mod default_keyword_for_insert {
183+
/// A marker trait indicating if a `DEFAULT` like expression
184+
/// is supported as part of `INSERT INTO` clauses to indicate
185+
/// that a default value should be inserted at a specific position
186+
///
187+
/// If you use a custom type to specify specialized support for `DEFAULT`
188+
/// expressions implementing this trait opts in support for `DEFAULT`
189+
/// value expressions for inserts. Otherwise diesel will emulate this
190+
/// behaviour.
191+
pub trait SupportsDefaultKeyword {}
192+
193+
/// Indicates that a backend support `DEFAULT` value expressions
194+
/// for `INSERT INTO` statements based on the ISO SQL standard
195+
#[derive(Debug, Copy, Clone)]
196+
pub struct IsoSqlDefaultKeyword;
197+
198+
/// Indicates that a backend does not support `DEFAULT` value
199+
/// expressions0for `INSERT INTO` statements
200+
#[derive(Debug, Copy, Clone)]
201+
pub struct DoesNotSupportDefaultKeyword;
202+
203+
impl SupportsDefaultKeyword for IsoSqlDefaultKeyword {}
204+
}
205+
206+
/// This module contains all reusable options to configure
207+
/// [`SqlDialect::BatchInsertSupport`]
208+
pub mod batch_insert_support {
209+
/// A marker trait indicating if batch insert statements
210+
/// are supported for this backend or not
211+
pub trait SupportsBatchInsert {}
212+
213+
/// Indicates that this backend does not support batch
214+
/// insert statements.
215+
/// In this case diesel will emulate batch insert support
216+
/// by inserting each row on it's own
217+
#[derive(Debug, Copy, Clone)]
218+
pub struct DoesNotSupportBatchInsert;
219+
220+
/// Indicates that this backend supports postgres style
221+
/// batch insert statements to insert multiple rows using one
222+
/// insert statement
223+
#[derive(Debug, Copy, Clone)]
224+
pub struct PostgresLikeBatchInsertSupport;
225+
226+
impl SupportsBatchInsert for PostgresLikeBatchInsertSupport {}
227+
}
228+
229+
/// This module contains all reusable options to configure
230+
/// [`SqlDialect::DefaultValueClauseForInsert`]
231+
pub mod default_value_clause {
232+
233+
/// Indicates that this backend uses the
234+
/// `DEFAULT VALUES` syntax to specify
235+
/// that a row consisting only of default
236+
/// values should be inserted
237+
#[derive(Debug, Clone, Copy)]
238+
pub struct AnsiDefaultValueClause;
239+
}
240+
241+
/// This module contains all reusable options to configure
242+
/// [`SqlDialect::EmptyFromClauseSyntax`]
243+
pub mod from_clause_syntax {
244+
245+
/// Indicates that this backend skips
246+
/// the `FROM` clause in `SELECT` statements
247+
/// if no table/view is queried
248+
#[derive(Debug, Copy, Clone)]
249+
pub struct AnsiSqlFromClauseSyntax;
250+
}
251+
252+
/// This module contains all reusable options to configure
253+
/// [`SqlDialect::ExistsSyntax`]
254+
pub mod exists_syntax {
255+
256+
/// Indicates that this backend
257+
/// treats `EXIST()` as function
258+
/// like expression
259+
#[derive(Debug, Copy, Clone)]
260+
pub struct AnsiSqlExistsSyntax;
261+
}
262+
263+
/// This module contains all reusable options to configure
264+
/// [`SqlDialect::ArrayComparision`]
265+
pub mod array_comparision {
266+
267+
/// Indicates that this backend requires a single bind
268+
/// per array element in `IN()` and `NOT IN()` expression
269+
#[derive(Debug, Copy, Clone)]
270+
pub struct AnsiSqlArrayComparison;
271+
}
272+
}

diesel/src/connection/transaction_manager.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::backend::UsesAnsiSavepointSyntax;
21
use crate::connection::Connection;
32
use crate::result::{DatabaseErrorKind, Error, QueryResult};
43

@@ -65,7 +64,6 @@ impl AnsiTransactionManager {
6564
pub fn begin_transaction_sql<Conn>(conn: &mut Conn, sql: &str) -> QueryResult<()>
6665
where
6766
Conn: Connection<TransactionManager = Self>,
68-
Conn::Backend: UsesAnsiSavepointSyntax,
6967
{
7068
use crate::result::Error::AlreadyInTransaction;
7169

@@ -81,7 +79,6 @@ impl AnsiTransactionManager {
8179
impl<Conn> TransactionManager<Conn> for AnsiTransactionManager
8280
where
8381
Conn: Connection<TransactionManager = Self>,
84-
Conn::Backend: UsesAnsiSavepointSyntax,
8582
{
8683
type TransactionStateData = Self;
8784

0 commit comments

Comments
 (0)