New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for `INSERT OR REPLACE` on SQLite #297

Merged
merged 1 commit into from Apr 23, 2016
Jump to file or symbol
Failed to load files and symbols.
+155 −27
Diff settings

Always

Just for now

View
@@ -85,6 +85,8 @@ pub mod prelude {
pub use prelude::*;
#[doc(inline)]
pub use query_builder::functions::{insert, update, delete, select};
#[cfg(feature = "sqlite")]
pub use sqlite::query_builder::functions::*;
pub use result::Error::NotFound;
#[doc(inline)]
pub use types::structs::data_types;
@@ -1,6 +1,7 @@
use expression::Expression;
use super::{UpdateTarget, IncompleteUpdateStatement, IncompleteInsertStatement, SelectStatement};
use super::delete_statement::DeleteStatement;
use super::insert_statement::Insert;
/// Creates an update statement. Helpers for updating a single row can be
/// generated by
@@ -108,8 +109,8 @@ pub fn delete<T: UpdateTarget>(source: T) -> DeleteStatement<T> {
/// Creates an insert statement. Will add the given data to a table. This
/// function is not exported by default. As with other commands, the resulting
/// query can return the inserted rows if you choose.
pub fn insert<'a, T: ?Sized>(records: &'a T) -> IncompleteInsertStatement<&'a T> {
IncompleteInsertStatement::new(records)
pub fn insert<'a, T: ?Sized>(records: &'a T) -> IncompleteInsertStatement<&'a T, Insert> {
IncompleteInsertStatement::new(records, Insert)
}
/// Creates a bare select statement, with no from clause. Primarily used for
@@ -7,43 +7,49 @@ use result::QueryResult;
/// The structure returned by [`insert`](fn.insert.html). The only thing that can be done with it
/// is call `into`.
pub struct IncompleteInsertStatement<T> {
pub struct IncompleteInsertStatement<T, Op> {
records: T,
operator: Op
}
impl<T> IncompleteInsertStatement<T> {
impl<T, Op> IncompleteInsertStatement<T, Op> {
#[doc(hidden)]
pub fn new(records: T) -> Self {
pub fn new(records: T, operator: Op) -> Self {
IncompleteInsertStatement {
records: records,
operator: operator,
}
}
/// Specify which table the data passed to `insert` should be added to.
pub fn into<S>(self, target: S) -> InsertStatement<S, T> where
InsertStatement<S, T>: AsQuery,
pub fn into<S>(self, target: S) -> InsertStatement<S, T, Op> where
InsertStatement<S, T, Op>: AsQuery,
{
InsertStatement {
operator: self.operator,
target: target,
records: self.records,
}
}
}
pub struct InsertStatement<T, U> {
pub struct InsertStatement<T, U, Op> {
operator: Op,
target: T,
records: U,
}
impl<T, U, DB> QueryFragment<DB> for InsertStatement<T, U> where
impl<T, U, Op, DB> QueryFragment<DB> for InsertStatement<T, U, Op> where
DB: Backend,
T: Table,
T::FromClause: QueryFragment<DB>,
U: Insertable<T, DB> + Copy,
Op: QueryFragment<DB>,
{
fn to_sql(&self, out: &mut DB::QueryBuilder) -> BuildQueryResult {
let values = self.records.values();
out.push_sql("INSERT INTO ");
try!(self.operator.to_sql(out));
out.push_sql(" INTO ");
try!(self.target.from_clause().to_sql(out));
out.push_sql(" (");
try!(values.column_names(out));
@@ -54,6 +60,7 @@ impl<T, U, DB> QueryFragment<DB> for InsertStatement<T, U> where
fn collect_binds(&self, out: &mut DB::BindCollector) -> QueryResult<()> {
let values = self.records.values();
try!(self.operator.collect_binds(out));
try!(self.target.from_clause().collect_binds(out));
try!(values.values_bind_params(out));
Ok(())
@@ -64,14 +71,14 @@ impl<T, U, DB> QueryFragment<DB> for InsertStatement<T, U> where
}
}
impl_query_id!(noop: InsertStatement<T, U>);
impl_query_id!(noop: InsertStatement<T, U, Op>);
impl<T, U> AsQuery for InsertStatement<T, U> where
impl<T, U, Op> AsQuery for InsertStatement<T, U, Op> where
T: Table,
InsertQuery<T::AllColumns, InsertStatement<T, U>>: Query,
InsertQuery<T::AllColumns, InsertStatement<T, U, Op>>: Query,
{
type SqlType = <Self::Query as Query>::SqlType;
type Query = InsertQuery<T::AllColumns, InsertStatement<T, U>>;
type Query = InsertQuery<T::AllColumns, Self>;
fn as_query(self) -> Self::Query {
InsertQuery {
@@ -81,7 +88,7 @@ impl<T, U> AsQuery for InsertStatement<T, U> where
}
}
impl<T, U> InsertStatement<T, U> {
impl<T, U, Op> InsertStatement<T, U, Op> {
/// Specify what expression is returned after execution of the `insert`.
/// # Examples
///
@@ -115,9 +122,9 @@ impl<T, U> InsertStatement<T, U> {
/// # #[cfg(not(feature = "postgres"))]
/// # fn main() {}
/// ```
pub fn returning<E>(self, returns: E) -> InsertQuery<E, InsertStatement<T, U>> where
E: Expression,
InsertQuery<E, InsertStatement<T, U>>: Query,
pub fn returning<E>(self, returns: E) -> InsertQuery<E, Self> where
E: Expression + SelectableExpression<T>,
InsertQuery<E, Self>: Query,
{
InsertQuery {
returning: returns,
@@ -132,9 +139,8 @@ pub struct InsertQuery<T, U> {
statement: U,
}
impl<T, U, V> Query for InsertQuery<T, InsertStatement<U, V>> where
U: Table,
T: Expression + SelectableExpression<U> + NonAggregate,
impl<T, U> Query for InsertQuery<T, U> where
T: Expression + NonAggregate,
{
type SqlType = T::SqlType;
}
@@ -163,3 +169,22 @@ impl<T, U, DB> QueryFragment<DB> for InsertQuery<T, U> where
}
impl_query_id!(noop: InsertQuery<T, U>);
pub struct Insert;
impl<DB: Backend> QueryFragment<DB> for Insert {
fn to_sql(&self, out: &mut DB::QueryBuilder) -> BuildQueryResult {
out.push_sql("INSERT");
Ok(())
}
fn collect_binds(&self, _out: &mut DB::BindCollector) -> QueryResult<()> {
Ok(())
}
fn is_safe_to_cache_prepared(&self) -> bool {
true
}
}
impl_query_id!(Insert);
View
@@ -1,8 +1,9 @@
mod backend;
mod connection;
mod query_builder;
mod types;
pub mod query_builder;
pub use self::backend::{Sqlite, SqliteType};
pub use self::connection::SqliteConnection;
pub use self::query_builder::SqliteQueryBuilder;
@@ -0,0 +1,26 @@
use expression::predicates::Or;
use query_builder::insert_statement::{IncompleteInsertStatement, Insert};
use super::nodes::Replace;
// FIXME: Replace this example with an actual running doctest once we have a
// more reasonable story for `impl Insertable` and friends without codegen
/// Creates a SQLite `INSERT OR REPLACE` statement. If a constraint violation
/// fails, SQLite will attempt to replace the offending row instead.
///
/// # Example
///
/// ```ignore
/// insert(&NewUser::new("Sean")).into(users).execute(&conn).unwrap();
/// insert(&NewUser::new("Tess")).into(users).execute(&conn).unwrap();
///
/// let new_user = User { id: 1, name: "Jim" };
/// insert_or_replace(&new_user).into(users).execute(&conn).unwrap();
///
/// let names = users.select(name).order(id).load::<String>(&conn);
/// assert_eq!(Ok(vec!["Jim".into(), "Tess".into()]), names);
/// ```
pub fn insert_or_replace<'a, T: ?Sized>(records: &'a T)
-> IncompleteInsertStatement<&'a T, Or<Insert, Replace>>
{
IncompleteInsertStatement::new(records, Or::new(Insert, Replace))
}
@@ -1,6 +1,10 @@
use super::backend::Sqlite;
use query_builder::{QueryBuilder, BuildQueryResult};
pub mod functions;
#[doc(hidden)]
pub mod nodes;
pub struct SqliteQueryBuilder {
pub sql: String,
}
@@ -0,0 +1,23 @@
use backend::Backend;
use query_builder::{QueryFragment, QueryBuilder, BuildQueryResult};
use result::QueryResult;
use sqlite::Sqlite;
pub struct Replace;
impl QueryFragment<Sqlite> for Replace {
fn to_sql(&self, out: &mut <Sqlite as Backend>::QueryBuilder) -> BuildQueryResult {
out.push_sql("REPLACE");
Ok(())
}
fn collect_binds(&self, _out: &mut <Sqlite as Backend>::BindCollector) -> QueryResult<()> {
Ok(())
}
fn is_safe_to_cache_prepared(&self) -> bool {
true
}
}
impl_query_id!(Replace);
@@ -3,7 +3,7 @@ extern crate diesel;
use diesel::*;
use diesel::backend::Backend;
use diesel::sqlite::*;
use diesel::sqlite::{Sqlite, SqliteQueryBuilder, SqliteConnection};
use diesel::types::{Integer, VarChar};
table! {
@@ -2,7 +2,7 @@
extern crate diesel;
use diesel::*;
use diesel::sqlite::*;
use diesel::sqlite::{Sqlite, SqliteQueryBuilder, SqliteConnection};
use diesel::backend::Backend;
use diesel::types::{Integer, VarChar};
@@ -0,0 +1,25 @@
#![feature(custom_derive, custom_attribute, plugin)]
#![plugin(diesel_codegen)]
#[macro_use]
extern crate diesel;
use diesel::*;
use diesel::pg::PgConnection;
table! {
users {
id -> Integer,
}
}
#[insertable_into(users)]
struct User {
id: i32,
}
fn main() {
let connection = PgConnection::establish("").unwrap();
insert_or_replace(&User { id: 1 }).into(users::table).execute(&connection).unwrap();
//~^ ERROR type mismatch resolving `<diesel::pg::PgConnection as diesel::Connection>::Backend == diesel::sqlite::Sqlite`
}
@@ -174,3 +174,23 @@ fn delete_records() {
assert_eq!(Ok(1), num_users);
}
#[test]
#[cfg(feature = "sqlite")]
fn insert_on_conflict_replace() {
use schema::users::dsl::*;
use diesel::insert_or_replace;
let connection = connection_with_sean_and_tess_in_users_table();
let mut sean = find_user_by_name("Sean", &connection);
sean.name = "Jim".into();
insert_or_replace(&sean)
.into(users)
.execute(&connection)
.unwrap();
let expected_names = vec!["Jim".into(), "Tess".into()];
let names = users.select(name).order(id).load::<String>(&connection);
assert_eq!(Ok(expected_names), names);
}
@@ -4,6 +4,7 @@ infer_schema!(dotenv!("DATABASE_URL"));
#[derive(PartialEq, Eq, Debug, Clone, Queryable)]
#[changeset_for(users)]
#[insertable_into(users)]
#[has_many(posts)]
pub struct User {
pub id: i32,
@@ -125,7 +126,7 @@ pub fn connection_without_transaction() -> TestConnection {
connection
}
use diesel::query_builder::insert_statement::InsertStatement;
use diesel::query_builder::insert_statement::{InsertStatement, Insert};
use diesel::query_builder::QueryFragment;
#[cfg(not(feature = "sqlite"))]
@@ -134,7 +135,7 @@ pub fn batch_insert<'a, T, U: 'a, Conn>(records: &'a [U], table: T, connection:
T: Table,
Conn: Connection,
&'a [U]: Insertable<T, Conn::Backend>,
InsertStatement<T, &'a [U]>: QueryFragment<Conn::Backend>,
InsertStatement<T, &'a [U], Insert>: QueryFragment<Conn::Backend>,
{
insert(records).into(table).execute(connection).unwrap()
}
@@ -145,7 +146,7 @@ pub fn batch_insert<'a, T, U: 'a, Conn>(records: &'a [U], table: T, connection:
T: Table + Copy,
Conn: Connection,
&'a U: Insertable<T, Conn::Backend>,
InsertStatement<T, &'a U>: QueryFragment<Conn::Backend>,
InsertStatement<T, &'a U, Insert>: QueryFragment<Conn::Backend>,
{
for record in records {
insert(record).into(table).execute(connection).unwrap();