Permalink
Browse files

Refactor `InsertValues` for tuples

This changes the implementation of `InsertValues` on tuples to not care
about its interior type, and moves the SQLite special handling to
`ColumnInsertValue` directly.

The addition of the `Table` parameter on `InsertValues` is required for
the tuple implementation to enforce that all values are targeting the
same table.

One side effect of this structure is that it will (eventually) allow
arbitrary nesting of `Insertable` types. This is important for use cases
like rocket, where you will likely have a `user_id` field which comes
from the session, but we want you to derive `Insertable` and `FromForm`
on the same struct. This means we should be able to write code like the
following:

    #[derive(Insertable, FromForm)]
    #[table_name = "posts"]
    struct NewPost {
        title: String,
        body: String,
    }

    #[post("/posts", data = "<form>")]
    fn create_post(data: NewPost, user: AuthenticatedUser, conn: DbConn)
        -> QueryResult<Redirect>
    {
        use schema::posts::dsl::*;

        let created = insert(&(user_id.eq(user.id), data))
            .into(posts)
            .returning(id)
            .get_result::<i32>(&*conn)?;
        let url = format!("/posts/{}", created);
        Ok(Redirect::to(&url))
    }

This should also let us simplify the `default_values` code dramatically.
  • Loading branch information...
sgrif committed Sep 14, 2017
1 parent 06e84ec commit 10374a5026bcab2202f7f67f057493c4636d547b
View
@@ -1,9 +1,10 @@
use std::marker::PhantomData;
use backend::{Backend, SupportsDefaultKeyword};
use expression::Expression;
use result::QueryResult;
use query_builder::AstPass;
use query_source::Table;
use query_builder::{AstPass, QueryBuilder, QueryFragment};
use query_source::{Column, Table};
#[cfg(feature = "sqlite")]
use sqlite::Sqlite;
/// Represents that a structure can be used to insert a new row into the
/// database. This is automatically implemented for `&[T]` and `&Vec<T>` for
@@ -17,14 +18,25 @@ use query_source::Table;
/// name of your struct differs from the name of the column, you can annotate
/// the field with `#[column_name = "some_column_name"]`.
pub trait Insertable<T: Table, DB: Backend> {
type Values: InsertValues<DB>;
type Values: InsertValues<T, DB>;
fn values(self) -> Self::Values;
}
pub trait InsertValues<DB: Backend> {
pub trait InsertValues<T: Table, DB: Backend> {
fn column_names(&self, out: &mut DB::QueryBuilder) -> QueryResult<()>;
fn walk_ast(&self, out: AstPass<DB>) -> QueryResult<()>;
/// Whether or not `column_names` and `walk_ast` will perform any action
///
/// This method will return `true` for values which semantically represent
/// `DEFAULT` on backends which don't support it, or `DEFAULT VALUES` on
/// any backend.
///
/// Note: This method only has semantic meaning for types which represent a
/// single row. Types which represent multiple rows will always return
/// `false` for this, even if they will insert 0 rows.
fn is_noop(&self) -> bool;
}
#[derive(Debug, Copy, Clone)]
@@ -33,18 +45,71 @@ pub enum ColumnInsertValue<Col, Expr> {
Default(Col),
}
impl<Col, Expr, DB> InsertValues<Col::Table, DB> for ColumnInsertValue<Col, Expr>
where
DB: Backend + SupportsDefaultKeyword,
Col: Column,
Expr: Expression<SqlType = Col::SqlType> + QueryFragment<DB>,
{
fn column_names(&self, out: &mut DB::QueryBuilder) -> QueryResult<()> {
out.push_identifier(Col::NAME)?;
Ok(())
}
fn walk_ast(&self, mut out: AstPass<DB>) -> QueryResult<()> {
if let ColumnInsertValue::Expression(_, ref value) = *self {
value.walk_ast(out.reborrow())?;
} else {
out.push_sql("DEFAULT");
}
Ok(())
}
fn is_noop(&self) -> bool {
false
}
}
#[cfg(feature = "sqlite")]
impl<Col, Expr> InsertValues<Col::Table, Sqlite> for ColumnInsertValue<Col, Expr>
where
Col: Column,
Expr: Expression<SqlType = Col::SqlType> + QueryFragment<Sqlite>,
{
fn column_names(&self, out: &mut <Sqlite as Backend>::QueryBuilder) -> QueryResult<()> {
if let ColumnInsertValue::Expression(..) = *self {
out.push_identifier(Col::NAME)?;
}
Ok(())
}
fn walk_ast(&self, mut out: AstPass<Sqlite>) -> QueryResult<()> {
if let ColumnInsertValue::Expression(_, ref value) = *self {
value.walk_ast(out.reborrow())?;
}
Ok(())
}
fn is_noop(&self) -> bool {
if let ColumnInsertValue::Expression(..) = *self {
false
} else {
true
}
}
}
impl<'a, T, Tab, DB> Insertable<Tab, DB> for &'a [T]
where
Tab: Table,
DB: Backend + SupportsDefaultKeyword,
&'a T: Insertable<Tab, DB>,
DB: Backend,
BatchInsertValues<'a, T>: InsertValues<Tab, DB>,
{
type Values = BatchInsertValues<'a, T, Tab>;
type Values = BatchInsertValues<'a, T>;
fn values(self) -> Self::Values {
BatchInsertValues {
records: self,
_marker: PhantomData,
}
}
}
@@ -63,12 +128,11 @@ where
}
#[derive(Debug, Clone)]
pub struct BatchInsertValues<'a, T: 'a, Tab> {
pub struct BatchInsertValues<'a, T: 'a> {
records: &'a [T],
_marker: PhantomData<Tab>,
}
impl<'a, T, Tab, DB> InsertValues<DB> for BatchInsertValues<'a, T, Tab>
impl<'a, T, Tab, DB> InsertValues<Tab, DB> for BatchInsertValues<'a, T>
where
Tab: Table,
DB: Backend + SupportsDefaultKeyword,
@@ -91,4 +155,8 @@ where
}
Ok(())
}
fn is_noop(&self) -> bool {
false
}
}
@@ -170,7 +170,7 @@ macro_rules! impl_Insertable {
$table_name::$column_name,
>,
>
,)+): $crate::insertable::InsertValues<DB>,
,)+): $crate::insertable::InsertValues<$table_name::table, DB>,
{
type Values = ($(
$crate::insertable::ColumnInsertValue<
@@ -96,9 +96,10 @@ pub struct OnConflictValues<Values, Target, Action> {
action: Action,
}
impl<Values, Target, Action> InsertValues<Pg> for OnConflictValues<Values, Target, Action>
impl<Tab, Values, Target, Action> InsertValues<Tab, Pg> for OnConflictValues<Values, Target, Action>
where
Values: InsertValues<Pg>,
Tab: Table,
Values: InsertValues<Tab, Pg>,
Target: QueryFragment<Pg>,
Action: QueryFragment<Pg>,
{
@@ -113,4 +114,8 @@ where
self.action.walk_ast(out.reborrow())?;
Ok(())
}
fn is_noop(&self) -> bool {
self.values.is_noop()
}
}
@@ -1,11 +1,11 @@
use std::error::Error;
use associations::BelongsTo;
use backend::{Backend, SupportsDefaultKeyword};
use backend::Backend;
use expression::{AppearsOnTable, Expression, NonAggregate, SelectableExpression};
use insertable::{ColumnInsertValue, InsertValues};
use insertable::InsertValues;
use query_builder::*;
use query_source::{Column, QuerySource, Queryable, Table};
use query_source::{QuerySource, Queryable, Table};
use result::QueryResult;
use row::Row;
use types::{FromSqlRow, HasSqlType, NotNull};
@@ -85,79 +85,48 @@ macro_rules! tuple_impls {
impl<$($T: Expression + NonAggregate),+> NonAggregate for ($($T,)+) {
}
#[cfg_attr(feature = "clippy", allow(eq_op))] // Clippy doesn't like the trivial case for 1-tuples
impl<$($T,)+ $($ST,)+ Tab, DB> InsertValues<DB>
for ($(ColumnInsertValue<$T, $ST>,)+) where
DB: Backend + SupportsDefaultKeyword,
Tab: Table,
$($T: Column<Table=Tab>,)+
$($ST: Expression<SqlType=$T::SqlType> + QueryFragment<DB>,)+
#[allow(unused_assignments)]
impl<$($T,)+ Tab, DB> InsertValues<Tab, DB> for ($($T,)+)
where
Tab: Table,
DB: Backend,
$($T: InsertValues<Tab, DB>,)+
{
fn column_names(&self, out: &mut DB::QueryBuilder) -> QueryResult<()> {
let mut needs_comma = false;
$(
if $idx != 0 {
out.push_sql(", ");
}
try!(out.push_identifier($T::NAME));
)+
Ok(())
}
fn walk_ast(&self, mut out: AstPass<DB>) -> QueryResult<()> {
out.push_sql("(");
$(
if $idx != 0 {
out.push_sql(", ");
}
if let ColumnInsertValue::Expression(_, ref value) = self.$idx {
value.walk_ast(out.reborrow())?;
} else {
out.push_sql("DEFAULT");
}
)+
out.push_sql(")");
Ok(())
}
}
#[cfg(feature = "sqlite")]
impl<$($T,)+ $($ST,)+ Tab> InsertValues<::sqlite::Sqlite>
for ($(ColumnInsertValue<$T, $ST>,)+) where
Tab: Table,
$($T: Column<Table=Tab>,)+
$($ST: Expression<SqlType=$T::SqlType> + QueryFragment<::sqlite::Sqlite>,)+
{
#[allow(unused_assignments)]
fn column_names(&self, out: &mut ::sqlite::SqliteQueryBuilder) -> QueryResult<()> {
let mut columns_present = false;
$(
if let ColumnInsertValue::Expression(..) = self.$idx {
if columns_present {
let noop_element = self.$idx.is_noop();
if !noop_element {
if needs_comma {
out.push_sql(", ");
}
try!(out.push_identifier($T::NAME));
columns_present = true;
self.$idx.column_names(out)?;
needs_comma = true;
}
)+
Ok(())
}
#[allow(unused_assignments)]
fn walk_ast(&self, mut out: AstPass<::sqlite::Sqlite>) -> QueryResult<()> {
fn walk_ast(&self, mut out: AstPass<DB>) -> QueryResult<()> {
out.push_sql("(");
let mut columns_present = false;
let mut needs_comma = false;
$(
if let ColumnInsertValue::Expression(_, ref value) = self.$idx {
if columns_present {
let noop_element = self.$idx.is_noop();
if !noop_element {
if needs_comma {
out.push_sql(", ");
}
value.walk_ast(out.reborrow())?;
columns_present = true;
self.$idx.walk_ast(out.reborrow())?;
needs_comma = true;
}
)+
out.push_sql(")");
Ok(())
}
fn is_noop(&self) -> bool {
$(self.$idx.is_noop() &&)+ true
}
}
impl<$($T,)+ QS> SelectableExpression<QS> for ($($T,)+) where

0 comments on commit 10374a5

Please sign in to comment.