Skip to content

Commit 0ee8363

Browse files
committed
Add support for INSERT OR REPLACE on SQLite
First piece of diesel-rs#196. SQLite has extremely simple semantics here. `INSERT OR REPLACE` will literally take what the new row would have been had the insert succeeded, and will replace the existing row with it. For this to be most useful, we'll probably want to make it easier to use non-literal expressions in insert at some point in the future. We'll need to see what other usage looks like. This also lays the groundwork for supporting the other modifiers on SQLite. I had originally planned on doing an API that looks more like what PG will look like. However, since the semantics are so different, we'll probably want to keep the APIs from stepping on each others toes. Another alternative I had leaned towards was `insert.or(replace)`. However, as I started thinking about the compile fail tests that'd be required there, and saw the implementation, I shied away from it. Since there's a fixed set of 4 modifiers that we will support, I think one function each is fine.
1 parent 06e3205 commit 0ee8363

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
@@ -85,6 +85,8 @@ pub mod prelude {
8585
pub use prelude::*;
8686
#[doc(inline)]
8787
pub use query_builder::functions::{insert, update, delete, select};
88+
#[cfg(feature = "sqlite")]
89+
pub use sqlite::query_builder::functions::*;
8890
pub use result::Error::NotFound;
8991
#[doc(inline)]
9092
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)