diesel-oci 0.2.0

A oci database adapter for diesel
Documentation
use diesel::insertable::{
    CanInsertInSingleQuery, ColumnInsertValue, DefaultableColumnInsertValue, InsertValues,
};
use diesel::query_builder::{
    AstPass, BatchInsert, InsertStatement, QueryFragment, QueryId, ValuesClause,
};
use diesel::query_dsl::methods::ExecuteDsl;
use diesel::result::QueryResult;
use diesel::{Connection, QuerySource, RunQueryDsl, Table};

use super::backend::Oracle;
use super::connection::OciConnection;

// This mostly mirrors the sqlite batch insert implementation in diesel
// see https://github.com/diesel-rs/diesel/blob/a843b608dcdc0cfc8b532f25d3579950312de190/diesel/src/query_builder/insert_statement/insert_with_default_for_sqlite.rs
//
// We try to solve two issues here:
//
// * Use a single query for batch inserts whenever possible
// * Support diesels default key word generated by `#[derive(Insertable)]` + Option fields
//
// For insert statements that cannot contain a default keyword we construct
// a single insert statement using select + union for the values
//
// For insert statements that can contain a default keyword we construct
// a sperate insert statement for each record
//
// To differentiate between both cases we need a bit of type system magic
// (that's mostly copied from diesel itself)
//
// Unfortunally we cannot provide the corresponding `Display`/`Debug` implementations
// for `DebugQuery` here due to the orphan rule

#[allow(missing_debug_implementations, missing_copy_implementations)]
pub struct Yes;

impl Default for Yes {
    fn default() -> Self {
        Yes
    }
}

#[allow(missing_debug_implementations, missing_copy_implementations)]
pub struct No;

impl Default for No {
    fn default() -> Self {
        No
    }
}

pub trait Any<Rhs> {
    type Out: Any<Yes> + Any<No>;
}

impl Any<No> for No {
    type Out = No;
}

impl Any<Yes> for No {
    type Out = Yes;
}

impl Any<No> for Yes {
    type Out = Yes;
}

impl Any<Yes> for Yes {
    type Out = Yes;
}

pub trait ContainsDefaultableValue {
    type Out: Any<Yes> + Any<No>;
}

impl<C, B> ContainsDefaultableValue for ColumnInsertValue<C, B> {
    type Out = No;
}

impl<I> ContainsDefaultableValue for DefaultableColumnInsertValue<I> {
    type Out = Yes;
}

impl<I, const SIZE: usize> ContainsDefaultableValue for [I; SIZE]
where
    I: ContainsDefaultableValue,
{
    type Out = I::Out;
}

impl<I, T> ContainsDefaultableValue for ValuesClause<I, T>
where
    I: ContainsDefaultableValue,
{
    type Out = I::Out;
}

impl<'a, T> ContainsDefaultableValue for &'a T
where
    T: ContainsDefaultableValue,
{
    type Out = T::Out;
}

macro_rules! tuple_impls {
        ($(
            $Tuple:tt {
                $(($idx:tt) -> $T:ident, $ST:ident, $TT:ident,)+
            }
        )+) => {
            $(
                impl_contains_defaultable_value!($($T,)*);
            )*
        }
    }

macro_rules! impl_contains_defaultable_value {
      (
        @build
        start_ts = [$($ST: ident,)*],
        ts = [$T1: ident,],
        bounds = [$($bounds: tt)*],
        out = [$($out: tt)*],
    )=> {
        impl<$($ST,)*> ContainsDefaultableValue for ($($ST,)*)
        where
            $($ST: ContainsDefaultableValue,)*
            $($bounds)*
            $T1::Out: Any<$($out)*>,
        {
            type Out = <$T1::Out as Any<$($out)*>>::Out;
        }

    };
    (
        @build
        start_ts = [$($ST: ident,)*],
        ts = [$T1: ident, $($T: ident,)+],
        bounds = [$($bounds: tt)*],
        out = [$($out: tt)*],
    )=> {
        impl_contains_defaultable_value! {
            @build
            start_ts = [$($ST,)*],
            ts = [$($T,)*],
            bounds = [$($bounds)* $T1::Out: Any<$($out)*>,],
            out = [<$T1::Out as Any<$($out)*>>::Out],
        }
    };
    ($T1: ident, $($T: ident,)+) => {
        impl_contains_defaultable_value! {
            @build
            start_ts = [$T1, $($T,)*],
            ts = [$($T,)*],
            bounds = [],
            out = [$T1::Out],
        }
    };
    ($T1: ident,) => {
        impl<$T1> ContainsDefaultableValue for ($T1,)
        where $T1: ContainsDefaultableValue,
        {
            type Out = <$T1 as ContainsDefaultableValue>::Out;
        }
    }
}

diesel_derives::__diesel_for_each_tuple!(tuple_impls);

impl<V, T, QId, Op, O, const STATIC_QUERY_ID: bool> ExecuteDsl<OciConnection, Oracle>
    for InsertStatement<T, BatchInsert<Vec<ValuesClause<V, T>>, T, QId, STATIC_QUERY_ID>, Op>
where
    T: QuerySource,
    V: ContainsDefaultableValue<Out = O>,
    O: Default,
    (O, Self): ExecuteDsl<OciConnection, Oracle>,
{
    fn execute(query: Self, conn: &mut OciConnection) -> QueryResult<usize> {
        <(O, Self) as ExecuteDsl<OciConnection, Oracle>>::execute((O::default(), query), conn)
    }
}

impl<V, T, QId, Op, const STATIC_QUERY_ID: bool> ExecuteDsl<OciConnection>
    for (
        Yes,
        InsertStatement<T, BatchInsert<Vec<ValuesClause<V, T>>, T, QId, STATIC_QUERY_ID>, Op>,
    )
where
    T: Table + Copy + QueryId + 'static,
    T::FromClause: QueryFragment<Oracle>,
    Op: Copy + QueryId + QueryFragment<Oracle>,
    V: InsertValues<T, Oracle> + CanInsertInSingleQuery<Oracle> + QueryId,
{
    fn execute((Yes, query): Self, conn: &mut OciConnection) -> QueryResult<usize> {
        conn.transaction(|conn| conn.batch_insert(query))
    }
}

#[allow(missing_debug_implementations, missing_copy_implementations)]
#[repr(transparent)]
pub struct OracleBatchInsertWrapper<V, T, QId, const STATIC_QUERY_ID: bool>(
    BatchInsert<V, T, QId, STATIC_QUERY_ID>,
);

// please refer to https://stackoverflow.com/questions/39576/best-way-to-do-multi-row-insert-in-oracle
impl<V, Tab, QId, const STATIC_QUERY_ID: bool> QueryFragment<Oracle>
    for OracleBatchInsertWrapper<Vec<ValuesClause<V, Tab>>, Tab, QId, STATIC_QUERY_ID>
where
    ValuesClause<V, Tab>: QueryFragment<Oracle>,
    Tab: Table,
    V: QueryFragment<Oracle> + InsertValues<Tab, Oracle>,
{
    fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Oracle>) -> QueryResult<()> {
        if !STATIC_QUERY_ID {
            out.unsafe_to_cache_prepared();
        }
        let mut records = self.0.values.iter();
        if let Some(record) = records.next() {
            out.push_sql("(");
            record.values.column_names(out.reborrow())?;
            out.push_sql(") ");
            out.push_sql("select ");
            record.values.walk_ast(out.reborrow())?;
            out.push_sql(" from dual");
        }
        for record in records {
            out.push_sql(" union all select ");
            record.values.walk_ast(out.reborrow())?;
            out.push_sql(" from dual");
        }
        Ok(())
    }
}

#[allow(missing_copy_implementations, missing_debug_implementations)]
#[repr(transparent)]
pub struct OracleCanInsertInSingleQueryHelper<T: ?Sized>(T);

impl<V, T, QId, const STATIC_QUERY_ID: bool> CanInsertInSingleQuery<Oracle>
    for OracleBatchInsertWrapper<Vec<ValuesClause<V, T>>, T, QId, STATIC_QUERY_ID>
where
    // We constrain that here on an internal helper type
    // to make sure that this does not accidently leak
    // so that noone does really implement normal batch
    // insert for inserts with default values here
    OracleCanInsertInSingleQueryHelper<V>: CanInsertInSingleQuery<Oracle>,
{
    fn rows_to_insert(&self) -> Option<usize> {
        Some(self.0.values.len())
    }
}

impl<T> CanInsertInSingleQuery<Oracle> for OracleCanInsertInSingleQueryHelper<T>
where
    T: CanInsertInSingleQuery<Oracle>,
{
    fn rows_to_insert(&self) -> Option<usize> {
        self.0.rows_to_insert()
    }
}

impl<V, T, QId, const STATIC_QUERY_ID: bool> QueryId
    for OracleBatchInsertWrapper<V, T, QId, STATIC_QUERY_ID>
where
    BatchInsert<V, T, QId, STATIC_QUERY_ID>: QueryId,
{
    type QueryId = <BatchInsert<V, T, QId, STATIC_QUERY_ID> as QueryId>::QueryId;

    const HAS_STATIC_QUERY_ID: bool =
        <BatchInsert<V, T, QId, STATIC_QUERY_ID> as QueryId>::HAS_STATIC_QUERY_ID;
}

impl<V, T, QId, Op, const STATIC_QUERY_ID: bool> ExecuteDsl<OciConnection, Oracle>
    for (
        No,
        InsertStatement<T, BatchInsert<V, T, QId, STATIC_QUERY_ID>, Op>,
    )
where
    T: Table + QueryId + 'static,
    T::FromClause: QueryFragment<Oracle>,
    Op: QueryFragment<Oracle> + QueryId,
    OracleBatchInsertWrapper<V, T, QId, STATIC_QUERY_ID>:
        QueryFragment<Oracle> + QueryId + CanInsertInSingleQuery<Oracle>,
{
    fn execute((No, query): Self, conn: &mut OciConnection) -> QueryResult<usize> {
        let query = InsertStatement::new(
            query.target,
            OracleBatchInsertWrapper(query.records),
            query.operator,
            query.returning,
        );
        query.execute(conn)
    }
}