Skip to content

Commit af875e1

Browse files
committed
Merge pull request diesel-rs#297 from diesel-rs/sg-upsert-sqlite
Add support for `INSERT OR REPLACE` on SQLite
2 parents 77ee50e + 0ee8363 commit af875e1

12 files changed

Lines changed: 155 additions & 27 deletions

File tree

diesel/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ pub mod prelude {
8787
pub use prelude::*;
8888
#[doc(inline)]
8989
pub use query_builder::functions::{insert, update, delete, select};
90+
#[cfg(feature = "sqlite")]
91+
pub use sqlite::query_builder::functions::*;
9092
pub use result::Error::NotFound;
9193
#[doc(inline)]
9294
pub use types::structs::data_types;

diesel/src/query_builder/functions.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use expression::Expression;
22
use super::{UpdateTarget, IncompleteUpdateStatement, IncompleteInsertStatement, SelectStatement};
33
use super::delete_statement::DeleteStatement;
4+
use super::insert_statement::Insert;
45

56
/// Creates an update statement. Helpers for updating a single row can be
67
/// generated by
@@ -108,8 +109,8 @@ pub fn delete<T: UpdateTarget>(source: T) -> DeleteStatement<T> {
108109
/// Creates an insert statement. Will add the given data to a table. This
109110
/// function is not exported by default. As with other commands, the resulting
110111
/// query can return the inserted rows if you choose.
111-
pub fn insert<'a, T: ?Sized>(records: &'a T) -> IncompleteInsertStatement<&'a T> {
112-
IncompleteInsertStatement::new(records)
112+
pub fn insert<'a, T: ?Sized>(records: &'a T) -> IncompleteInsertStatement<&'a T, Insert> {
113+
IncompleteInsertStatement::new(records, Insert)
113114
}
114115

115116
/// Creates a bare select statement, with no from clause. Primarily used for

diesel/src/query_builder/insert_statement.rs

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,49 @@ use result::QueryResult;
77

88
/// The structure returned by [`insert`](fn.insert.html). The only thing that can be done with it
99
/// is call `into`.
10-
pub struct IncompleteInsertStatement<T> {
10+
pub struct IncompleteInsertStatement<T, Op> {
1111
records: T,
12+
operator: Op
1213
}
1314

14-
impl<T> IncompleteInsertStatement<T> {
15+
impl<T, Op> IncompleteInsertStatement<T, Op> {
1516
#[doc(hidden)]
16-
pub fn new(records: T) -> Self {
17+
pub fn new(records: T, operator: Op) -> Self {
1718
IncompleteInsertStatement {
1819
records: records,
20+
operator: operator,
1921
}
2022
}
2123

2224
/// Specify which table the data passed to `insert` should be added to.
23-
pub fn into<S>(self, target: S) -> InsertStatement<S, T> where
24-
InsertStatement<S, T>: AsQuery,
25+
pub fn into<S>(self, target: S) -> InsertStatement<S, T, Op> where
26+
InsertStatement<S, T, Op>: AsQuery,
2527
{
2628
InsertStatement {
29+
operator: self.operator,
2730
target: target,
2831
records: self.records,
2932
}
3033
}
3134
}
3235

33-
pub struct InsertStatement<T, U> {
36+
pub struct InsertStatement<T, U, Op> {
37+
operator: Op,
3438
target: T,
3539
records: U,
3640
}
3741

38-
impl<T, U, DB> QueryFragment<DB> for InsertStatement<T, U> where
42+
impl<T, U, Op, DB> QueryFragment<DB> for InsertStatement<T, U, Op> where
3943
DB: Backend,
4044
T: Table,
4145
T::FromClause: QueryFragment<DB>,
4246
U: Insertable<T, DB> + Copy,
47+
Op: QueryFragment<DB>,
4348
{
4449
fn to_sql(&self, out: &mut DB::QueryBuilder) -> BuildQueryResult {
4550
let values = self.records.values();
46-
out.push_sql("INSERT INTO ");
51+
try!(self.operator.to_sql(out));
52+
out.push_sql(" INTO ");
4753
try!(self.target.from_clause().to_sql(out));
4854
out.push_sql(" (");
4955
try!(values.column_names(out));
@@ -54,6 +60,7 @@ impl<T, U, DB> QueryFragment<DB> for InsertStatement<T, U> where
5460

5561
fn collect_binds(&self, out: &mut DB::BindCollector) -> QueryResult<()> {
5662
let values = self.records.values();
63+
try!(self.operator.collect_binds(out));
5764
try!(self.target.from_clause().collect_binds(out));
5865
try!(values.values_bind_params(out));
5966
Ok(())
@@ -64,14 +71,14 @@ impl<T, U, DB> QueryFragment<DB> for InsertStatement<T, U> where
6471
}
6572
}
6673

67-
impl_query_id!(noop: InsertStatement<T, U>);
74+
impl_query_id!(noop: InsertStatement<T, U, Op>);
6875

69-
impl<T, U> AsQuery for InsertStatement<T, U> where
76+
impl<T, U, Op> AsQuery for InsertStatement<T, U, Op> where
7077
T: Table,
71-
InsertQuery<T::AllColumns, InsertStatement<T, U>>: Query,
78+
InsertQuery<T::AllColumns, InsertStatement<T, U, Op>>: Query,
7279
{
7380
type SqlType = <Self::Query as Query>::SqlType;
74-
type Query = InsertQuery<T::AllColumns, InsertStatement<T, U>>;
81+
type Query = InsertQuery<T::AllColumns, Self>;
7582

7683
fn as_query(self) -> Self::Query {
7784
InsertQuery {
@@ -81,7 +88,7 @@ impl<T, U> AsQuery for InsertStatement<T, U> where
8188
}
8289
}
8390

84-
impl<T, U> InsertStatement<T, U> {
91+
impl<T, U, Op> InsertStatement<T, U, Op> {
8592
/// Specify what expression is returned after execution of the `insert`.
8693
/// # Examples
8794
///
@@ -115,9 +122,9 @@ impl<T, U> InsertStatement<T, U> {
115122
/// # #[cfg(not(feature = "postgres"))]
116123
/// # fn main() {}
117124
/// ```
118-
pub fn returning<E>(self, returns: E) -> InsertQuery<E, InsertStatement<T, U>> where
119-
E: Expression,
120-
InsertQuery<E, InsertStatement<T, U>>: Query,
125+
pub fn returning<E>(self, returns: E) -> InsertQuery<E, Self> where
126+
E: Expression + SelectableExpression<T>,
127+
InsertQuery<E, Self>: Query,
121128
{
122129
InsertQuery {
123130
returning: returns,
@@ -132,9 +139,8 @@ pub struct InsertQuery<T, U> {
132139
statement: U,
133140
}
134141

135-
impl<T, U, V> Query for InsertQuery<T, InsertStatement<U, V>> where
136-
U: Table,
137-
T: Expression + SelectableExpression<U> + NonAggregate,
142+
impl<T, U> Query for InsertQuery<T, U> where
143+
T: Expression + NonAggregate,
138144
{
139145
type SqlType = T::SqlType;
140146
}
@@ -163,3 +169,22 @@ impl<T, U, DB> QueryFragment<DB> for InsertQuery<T, U> where
163169
}
164170

165171
impl_query_id!(noop: InsertQuery<T, U>);
172+
173+
pub struct Insert;
174+
175+
impl<DB: Backend> QueryFragment<DB> for Insert {
176+
fn to_sql(&self, out: &mut DB::QueryBuilder) -> BuildQueryResult {
177+
out.push_sql("INSERT");
178+
Ok(())
179+
}
180+
181+
fn collect_binds(&self, _out: &mut DB::BindCollector) -> QueryResult<()> {
182+
Ok(())
183+
}
184+
185+
fn is_safe_to_cache_prepared(&self) -> bool {
186+
true
187+
}
188+
}
189+
190+
impl_query_id!(Insert);

diesel/src/sqlite/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
mod backend;
22
mod connection;
3-
mod query_builder;
43
mod types;
54

5+
pub mod query_builder;
6+
67
pub use self::backend::{Sqlite, SqliteType};
78
pub use self::connection::SqliteConnection;
89
pub use self::query_builder::SqliteQueryBuilder;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use expression::predicates::Or;
2+
use query_builder::insert_statement::{IncompleteInsertStatement, Insert};
3+
use super::nodes::Replace;
4+
5+
// FIXME: Replace this example with an actual running doctest once we have a
6+
// more reasonable story for `impl Insertable` and friends without codegen
7+
/// Creates a SQLite `INSERT OR REPLACE` statement. If a constraint violation
8+
/// fails, SQLite will attempt to replace the offending row instead.
9+
///
10+
/// # Example
11+
///
12+
/// ```ignore
13+
/// insert(&NewUser::new("Sean")).into(users).execute(&conn).unwrap();
14+
/// insert(&NewUser::new("Tess")).into(users).execute(&conn).unwrap();
15+
///
16+
/// let new_user = User { id: 1, name: "Jim" };
17+
/// insert_or_replace(&new_user).into(users).execute(&conn).unwrap();
18+
///
19+
/// let names = users.select(name).order(id).load::<String>(&conn);
20+
/// assert_eq!(Ok(vec!["Jim".into(), "Tess".into()]), names);
21+
/// ```
22+
pub fn insert_or_replace<'a, T: ?Sized>(records: &'a T)
23+
-> IncompleteInsertStatement<&'a T, Or<Insert, Replace>>
24+
{
25+
IncompleteInsertStatement::new(records, Or::new(Insert, Replace))
26+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use super::backend::Sqlite;
22
use query_builder::{QueryBuilder, BuildQueryResult};
33

4+
pub mod functions;
5+
#[doc(hidden)]
6+
pub mod nodes;
7+
48
pub struct SqliteQueryBuilder {
59
pub sql: String,
610
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use backend::Backend;
2+
use query_builder::{QueryFragment, QueryBuilder, BuildQueryResult};
3+
use result::QueryResult;
4+
use sqlite::Sqlite;
5+
6+
pub struct Replace;
7+
8+
impl QueryFragment<Sqlite> for Replace {
9+
fn to_sql(&self, out: &mut <Sqlite as Backend>::QueryBuilder) -> BuildQueryResult {
10+
out.push_sql("REPLACE");
11+
Ok(())
12+
}
13+
14+
fn collect_binds(&self, _out: &mut <Sqlite as Backend>::BindCollector) -> QueryResult<()> {
15+
Ok(())
16+
}
17+
18+
fn is_safe_to_cache_prepared(&self) -> bool {
19+
true
20+
}
21+
}
22+
23+
impl_query_id!(Replace);

diesel_compile_tests/tests/compile-fail/batch_insert_is_not_supported_on_sqlite.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ extern crate diesel;
33

44
use diesel::*;
55
use diesel::backend::Backend;
6-
use diesel::sqlite::*;
6+
use diesel::sqlite::{Sqlite, SqliteQueryBuilder, SqliteConnection};
77
use diesel::types::{Integer, VarChar};
88

99
table! {

diesel_compile_tests/tests/compile-fail/insert_statement_does_not_support_returning_methods_on_sqlite.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
extern crate diesel;
33

44
use diesel::*;
5-
use diesel::sqlite::*;
5+
use diesel::sqlite::{Sqlite, SqliteQueryBuilder, SqliteConnection};
66
use diesel::backend::Backend;
77
use diesel::types::{Integer, VarChar};
88

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#![feature(custom_derive, custom_attribute, plugin)]
2+
#![plugin(diesel_codegen)]
3+
4+
#[macro_use]
5+
extern crate diesel;
6+
7+
use diesel::*;
8+
use diesel::pg::PgConnection;
9+
10+
table! {
11+
users {
12+
id -> Integer,
13+
}
14+
}
15+
16+
#[insertable_into(users)]
17+
struct User {
18+
id: i32,
19+
}
20+
21+
fn main() {
22+
let connection = PgConnection::establish("").unwrap();
23+
insert_or_replace(&User { id: 1 }).into(users::table).execute(&connection).unwrap();
24+
//~^ ERROR type mismatch resolving `<diesel::pg::PgConnection as diesel::Connection>::Backend == diesel::sqlite::Sqlite`
25+
}

0 commit comments

Comments
 (0)