Skip to content

Commit 49e3cdc

Browse files
authored
Merge pull request diesel-rs#519 from diesel-rs/sg-batch-insert-sqlite
Support batch insert on SQLite, fix inserting empty slices
2 parents 0f04e34 + c07124e commit 49e3cdc

6 files changed

Lines changed: 251 additions & 75 deletions

File tree

diesel/src/macros/insertable.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,22 @@ macro_rules! _Insertable {
199199
,)+)
200200
}
201201
}
202+
203+
} __diesel_parse_as_item! {
204+
impl<$($lifetime: 'insert,)* 'insert, Op, Ret> $crate::query_builder::insert_statement::IntoInsertStatement<$table_name::table, Op, Ret>
205+
for &'insert $struct_ty
206+
{
207+
type InsertStatement = $crate::query_builder::insert_statement::InsertStatement<$table_name::table, Self, Op, Ret>;
208+
209+
fn into_insert_statement(self, target: $table_name::table, operator: Op, returning: Ret) -> Self::InsertStatement {
210+
$crate::query_builder::insert_statement::InsertStatement::new(
211+
target,
212+
self,
213+
operator,
214+
returning,
215+
)
216+
}
217+
}
202218
}};
203219
}
204220

diesel/src/query_builder/insert_statement.rs

Lines changed: 184 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
use backend::Backend;
1+
use backend::{Backend, SupportsDefaultKeyword};
2+
use connection::Connection;
23
use expression::{Expression, SelectableExpression, NonAggregate};
34
use persistable::{Insertable, InsertValues};
45
use query_builder::*;
5-
use query_source::Table;
6+
use query_dsl::{ExecuteDsl, LoadDsl};
7+
use query_source::{Queryable, Table};
68
use result::QueryResult;
79
use super::returning_clause::*;
10+
use types::HasSqlType;
811

912
/// The structure returned by [`insert`](fn.insert.html). The only thing that can be done with it
1013
/// is call `into`.
@@ -24,26 +27,64 @@ impl<T, Op> IncompleteInsertStatement<T, Op> {
2427
}
2528

2629
/// Specify which table the data passed to `insert` should be added to.
27-
pub fn into<S>(self, target: S) -> InsertStatement<S, T, Op> where
28-
InsertStatement<S, T, Op>: AsQuery,
30+
pub fn into<S>(self, target: S) -> T::InsertStatement where
31+
T: IntoInsertStatement<S, Op, NoReturningClause>,
2932
{
30-
InsertStatement {
31-
operator: self.operator,
33+
self.records.into_insert_statement(target, self.operator, NoReturningClause)
34+
}
35+
}
36+
37+
pub trait IntoInsertStatement<Tab, Op=Insert, Ret=NoReturningClause> {
38+
type InsertStatement;
39+
40+
fn into_insert_statement(self, target: Tab, operator: Op, returning: Ret)
41+
-> Self::InsertStatement;
42+
}
43+
44+
impl<'a, T, Tab, Op, Ret> IntoInsertStatement<Tab, Op, Ret> for &'a [T] {
45+
type InsertStatement = BatchInsertStatement<Tab, Self, Op, Ret>;
46+
47+
fn into_insert_statement(self, target: Tab, operator: Op, returning: Ret)
48+
-> Self::InsertStatement
49+
{
50+
BatchInsertStatement {
51+
operator: operator,
3252
target: target,
33-
records: self.records,
34-
returning: NoReturningClause,
53+
records: self,
54+
returning: returning,
3555
}
3656
}
3757
}
3858

59+
impl<'a, T, Tab, Op, Ret> IntoInsertStatement<Tab, Op, Ret> for &'a Vec<T> {
60+
type InsertStatement = <&'a [T] as IntoInsertStatement<Tab, Op, Ret>>::InsertStatement;
61+
62+
fn into_insert_statement(self, target: Tab, operator: Op, returning: Ret)
63+
-> Self::InsertStatement
64+
{
65+
(&**self).into_insert_statement(target, operator, returning)
66+
}
67+
}
68+
3969
#[derive(Debug, Copy, Clone)]
40-
pub struct InsertStatement<T, U, Op, Ret=NoReturningClause> {
70+
pub struct InsertStatement<T, U, Op=Insert, Ret=NoReturningClause> {
4171
operator: Op,
4272
target: T,
4373
records: U,
4474
returning: Ret,
4575
}
4676

77+
impl<T, U, Op, Ret> InsertStatement<T, U, Op, Ret> {
78+
pub fn new(target: T, records: U, operator: Op, returning: Ret) -> Self {
79+
InsertStatement {
80+
operator: operator,
81+
target: target,
82+
records: records,
83+
returning: returning,
84+
}
85+
}
86+
}
87+
4788
impl<T, U, Op, Ret, DB> QueryFragment<DB> for InsertStatement<T, U, Op, Ret> where
4889
DB: Backend,
4990
T: Table,
@@ -148,6 +189,140 @@ impl<T, U, Op> InsertStatement<T, U, Op, NoReturningClause> {
148189
}
149190
}
150191

192+
#[derive(Debug, Clone, Copy)]
193+
/// The result of calling `insert(records).into(some_table)` when `records` is
194+
/// a slice or a `Vec`. When calling methods from `ExecuteDsl` or `LoadDsl`.
195+
/// When the given slice is empty, this struct will not execute any queries.
196+
/// When the given slice is not empty, this will execute a single bulk insert
197+
/// on backends which support the `DEFAULT` keyword, and one query per record
198+
/// on backends which do not (SQLite).
199+
pub struct BatchInsertStatement<T, U, Op=Insert, Ret=NoReturningClause> {
200+
operator: Op,
201+
target: T,
202+
records: U,
203+
returning: Ret,
204+
}
205+
206+
impl<T, U, Op, Ret> BatchInsertStatement<T, U, Op, Ret> {
207+
fn into_insert_statement(self) -> InsertStatement<T, U, Op, Ret> {
208+
InsertStatement::new(
209+
self.target,
210+
self.records,
211+
self.operator,
212+
self.returning,
213+
)
214+
}
215+
}
216+
217+
impl<T, U, Op> BatchInsertStatement<T, U, Op> {
218+
/// Specify what expression is returned after execution of the `insert`.
219+
/// # Examples
220+
///
221+
/// ### Inserting records:
222+
///
223+
/// ```rust
224+
/// # #[macro_use] extern crate diesel;
225+
/// # include!("src/doctest_setup.rs");
226+
/// #
227+
/// # table! {
228+
/// # users {
229+
/// # id -> Integer,
230+
/// # name -> VarChar,
231+
/// # }
232+
/// # }
233+
/// #
234+
/// # #[cfg(feature = "postgres")]
235+
/// # fn main() {
236+
/// # use self::users::dsl::*;
237+
/// # let connection = establish_connection();
238+
/// let new_users = vec![
239+
/// NewUser { name: "Timmy".to_string(), },
240+
/// NewUser { name: "Jimmy".to_string(), },
241+
/// ];
242+
///
243+
/// let inserted_names = diesel::insert(&new_users)
244+
/// .into(users)
245+
/// .returning(name)
246+
/// .get_results(&connection);
247+
/// assert_eq!(Ok(vec!["Timmy".to_string(), "Jimmy".to_string()]), inserted_names);
248+
/// # }
249+
/// # #[cfg(not(feature = "postgres"))]
250+
/// # fn main() {}
251+
/// ```
252+
pub fn returning<E>(self, returns: E)
253+
-> BatchInsertStatement<T, U, Op, ReturningClause<E>>
254+
{
255+
BatchInsertStatement {
256+
operator: self.operator,
257+
target: self.target,
258+
records: self.records,
259+
returning: ReturningClause(returns),
260+
}
261+
}
262+
}
263+
264+
impl<'a, T, U, Op, Ret, Conn, DB> ExecuteDsl<Conn, DB>
265+
for BatchInsertStatement<T, &'a [U], Op, Ret> where
266+
Conn: Connection<Backend=DB>,
267+
DB: Backend + SupportsDefaultKeyword,
268+
InsertStatement<T, &'a [U], Op, Ret>: ExecuteDsl<Conn>,
269+
{
270+
fn execute(self, conn: &Conn) -> QueryResult<usize> {
271+
if self.records.is_empty() {
272+
Ok(0)
273+
} else {
274+
self.into_insert_statement().execute(conn)
275+
}
276+
}
277+
}
278+
279+
#[cfg(feature="sqlite")]
280+
impl<'a, T, U, Op, Ret> ExecuteDsl<::sqlite::SqliteConnection>
281+
for BatchInsertStatement<T, &'a [U], Op, Ret> where
282+
InsertStatement<T, &'a U, Op, Ret>: ExecuteDsl<::sqlite::SqliteConnection>,
283+
T: Copy,
284+
Op: Copy,
285+
Ret: Copy,
286+
{
287+
fn execute(self, conn: &::sqlite::SqliteConnection) -> QueryResult<usize> {
288+
let mut result = 0;
289+
for record in self.records {
290+
result += InsertStatement::new(self.target, record, self.operator, self.returning)
291+
.execute(conn)?;
292+
}
293+
Ok(result)
294+
}
295+
}
296+
297+
impl<'a, T, U, Op, Ret, Conn, ST> LoadDsl<Conn>
298+
for BatchInsertStatement<T, &'a [U], Op, Ret> where
299+
Conn: Connection,
300+
Conn::Backend: HasSqlType<ST>,
301+
InsertStatement<T, &'a [U], Op, Ret>: LoadDsl<Conn, SqlType=ST>,
302+
{
303+
type SqlType = ST;
304+
305+
fn load<'b, V>(self, conn: &Conn) -> QueryResult<Vec<V>> where
306+
V: Queryable<Self::SqlType, Conn::Backend> + 'b,
307+
{
308+
if self.records.is_empty() {
309+
Ok(Vec::new())
310+
} else {
311+
self.into_insert_statement().load(conn)
312+
}
313+
}
314+
315+
fn get_result<V>(self, conn: &Conn) -> QueryResult<V> where
316+
V: Queryable<Self::SqlType, Conn::Backend>,
317+
{
318+
if self.records.is_empty() {
319+
Err(::result::Error::NotFound)
320+
} else {
321+
self.into_insert_statement().get_result(conn)
322+
}
323+
}
324+
}
325+
151326
#[derive(Debug, Copy, Clone)]
152327
pub struct Insert;
153328

diesel/src/query_dsl/load_dsl.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use backend::Backend;
12
use connection::Connection;
23
use helper_types::Limit;
34
use query_builder::{QueryFragment, AsQuery, QueryId};
@@ -8,9 +9,11 @@ use types::HasSqlType;
89

910
/// Methods to execute a query given a connection. These are automatically implemented for the
1011
/// various query types.
11-
pub trait LoadDsl<Conn: Connection>: AsQuery + Sized where
12+
pub trait LoadDsl<Conn: Connection>: Sized where
1213
Conn::Backend: HasSqlType<Self::SqlType>,
1314
{
15+
type SqlType;
16+
1417
/// Executes the given query, returning an `Iterator` over the returned
1518
/// rows.
1619
fn load<'a, U>(self, conn: &Conn) -> QueryResult<Vec<U>> where
@@ -22,8 +25,8 @@ pub trait LoadDsl<Conn: Connection>: AsQuery + Sized where
2225
/// `Result<Option<U>>`.
2326
fn first<U>(self, conn: &Conn) -> QueryResult<U> where
2427
Self: LimitDsl,
25-
Limit<Self>: LoadDsl<Conn, SqlType=Self::SqlType>,
26-
U: Queryable<Self::SqlType, Conn::Backend>,
28+
Limit<Self>: LoadDsl<Conn, SqlType=<Self as LoadDsl<Conn>>::SqlType>,
29+
U: Queryable<<Self as LoadDsl<Conn>>::SqlType, Conn::Backend>,
2730
{
2831
self.limit(1).get_result(conn)
2932
}
@@ -47,6 +50,8 @@ impl<Conn: Connection, T: AsQuery> LoadDsl<Conn> for T where
4750
T::Query: QueryFragment<Conn::Backend> + QueryId,
4851
Conn::Backend: HasSqlType<T::SqlType>,
4952
{
53+
type SqlType = <Self as AsQuery>::SqlType;
54+
5055
fn load<'a, U>(self, conn: &Conn) -> QueryResult<Vec<U>> where
5156
U: Queryable<Self::SqlType, Conn::Backend> + 'a,
5257
{
@@ -60,18 +65,19 @@ impl<Conn: Connection, T: AsQuery> LoadDsl<Conn> for T where
6065
}
6166
}
6267

63-
pub trait ExecuteDsl<Conn: Connection>: Sized + QueryFragment<Conn::Backend> + QueryId {
68+
pub trait ExecuteDsl<Conn: Connection<Backend=DB>, DB: Backend = <Conn as Connection>::Backend>: Sized {
6469
/// Executes the given command, returning the number of rows affected. Used
6570
/// in conjunction with
6671
/// [`update`](../query_builder/fn.update.html) and
6772
/// [`delete`](../query_builder/fn.delete.html)
68-
fn execute(&self, conn: &Conn) -> QueryResult<usize> {
69-
conn.execute_returning_count(self)
70-
}
73+
fn execute(self, conn: &Conn) -> QueryResult<usize>;
7174
}
7275

7376
impl<Conn, T> ExecuteDsl<Conn> for T where
7477
Conn: Connection,
7578
T: QueryFragment<Conn::Backend> + QueryId,
7679
{
80+
fn execute(self, conn: &Conn) -> QueryResult<usize> {
81+
conn.execute_returning_count(&self)
82+
}
7783
}

diesel_compile_tests/tests/compile-fail/batch_insert_is_not_supported_on_sqlite.rs

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)