use std::marker::PhantomData;
use rorm_db::transaction::Transaction;
use rorm_db::{error::Error, Database, Row};
use crate::conditions::Value;
use crate::crud::builder::TransactionMarker;
use crate::internal::field::as_db_type::AsDbType;
use crate::internal::field::FieldProxy;
use crate::internal::field::{Field, RawField};
use crate::model::{iter_columns, Model, Patch};
#[must_use]
pub struct InsertBuilder<'db, 'rf, P, T, R> {
db: &'db Database,
transaction: T,
returning: R,
_phantom: PhantomData<&'rf P>,
}
impl<'db, 'rf, P> InsertBuilder<'db, 'rf, P, (), PrimaryKey<P::Model>>
where
P: Patch,
{
pub fn new(db: &'db Database) -> Self {
InsertBuilder {
db,
transaction: (),
returning: PrimaryKey::new(),
_phantom: PhantomData,
}
}
}
impl<'db, 'rf, P, R> InsertBuilder<'db, 'rf, P, (), R> {
pub fn transaction(
self,
transaction: &'rf mut Transaction<'db>,
) -> InsertBuilder<'db, 'rf, P, &'rf mut Transaction<'db>, R> {
#[rustfmt::skip]
let InsertBuilder { db, returning, _phantom, .. } = self;
#[rustfmt::skip]
return InsertBuilder { db, transaction, _phantom, returning };
}
}
impl<'db, 'rf, P, T, M> InsertBuilder<'db, 'rf, P, T, PrimaryKey<M>>
where
M: Model,
P: Patch<Model = M>,
{
pub fn return_tuple<Return, const C: usize>(
self,
tuple: Return,
) -> InsertBuilder<'db, 'rf, P, T, ReturnTuple<Return, C>>
where
ReturnTuple<Return, C>: Returning<M> + From<Return>,
{
#[rustfmt::skip]
let InsertBuilder { db, transaction, _phantom, .. } = self;
#[rustfmt::skip]
return InsertBuilder { db, transaction, returning: tuple.into(), _phantom };
}
pub fn return_patch<Return>(self) -> InsertBuilder<'db, 'rf, P, T, ReturnPatch<Return>>
where
Return: Patch<Model = M>,
{
#[rustfmt::skip]
let InsertBuilder { db, transaction, _phantom, .. } = self;
#[rustfmt::skip]
return InsertBuilder { db, transaction, returning: ReturnPatch::new(), _phantom };
}
}
impl<'db, 'rf, P, T, R> InsertBuilder<'db, 'rf, P, T, R>
where
'db: 'rf,
P: Patch,
T: TransactionMarker<'rf, 'db>,
R: Returning<P::Model>,
{
pub async fn single(self, patch: &'rf P) -> Result<R::Result, Error> {
let values = Vec::from_iter(iter_columns(patch).map(Value::into_sql));
let columns = Vec::from_iter(P::COLUMNS.iter().flatten().cloned());
self.db
.insert_returning(
P::Model::TABLE,
&columns,
&values,
self.transaction.into_option(),
self.returning.columns(),
)
.await
.and_then(R::decode)
}
pub async fn bulk(
self,
patches: impl IntoIterator<Item = &'rf P>,
) -> Result<Vec<R::Result>, Error> {
let mut values = Vec::new();
for patch in patches {
values.push(Vec::from_iter(iter_columns(patch).map(Value::into_sql)));
}
let values_slices = Vec::from_iter(values.iter().map(Vec::as_slice));
let columns = Vec::from_iter(P::COLUMNS.iter().flatten().cloned());
self.db
.insert_bulk_returning(
P::Model::TABLE,
&columns,
&values_slices,
self.transaction.into_option(),
self.returning.columns(),
)
.await
.and_then(|rows| {
rows.into_iter()
.map(R::decode)
.collect::<Result<Vec<_>, _>>()
})
}
}
#[macro_export]
macro_rules! insert {
($db:expr, $patch:path) => {
$crate::crud::insert::InsertBuilder::<$patch, _, _>::new($db)
};
}
pub trait Returning<M: Model> {
type Result;
fn decode(row: Row) -> Result<Self::Result, Error>;
fn columns(&self) -> &[&'static str];
}
pub struct PrimaryKey<M> {
columns: [&'static str; 1],
model: PhantomData<M>,
}
impl<M: Model> PrimaryKey<M> {
const fn new() -> Self {
Self {
columns: [M::Primary::NAME],
model: PhantomData,
}
}
}
impl<M: Model> Returning<M> for PrimaryKey<M> {
type Result = <M::Primary as Field>::Type;
fn decode(row: Row) -> Result<Self::Result, Error> {
Ok(<M::Primary as Field>::Type::from_primitive(row.get(0)?))
}
fn columns(&self) -> &[&'static str] {
&self.columns
}
}
pub struct ReturnPatch<P: Patch> {
patch: PhantomData<P>,
columns: Vec<&'static str>,
}
impl<P: Patch> ReturnPatch<P> {
pub fn new() -> Self {
Self {
patch: PhantomData,
columns: P::COLUMNS.iter().flatten().copied().collect(),
}
}
}
impl<P: Patch> Default for ReturnPatch<P> {
fn default() -> Self {
Self::new()
}
}
impl<M: Model, P: Patch<Model = M>> Returning<M> for ReturnPatch<P> {
type Result = P;
fn decode(row: Row) -> Result<Self::Result, Error> {
P::from_row_using_position(row)
}
fn columns(&self) -> &[&'static str] {
&self.columns
}
}
pub struct ReturnTuple<T, const C: usize> {
#[allow(dead_code)]
tuple: T,
columns: [&'static str; C],
}
macro_rules! impl_select_tuple {
($C:literal, ($($F:ident @ $i:literal),+)) => {
impl<$($F: Field),+> From<($(FieldProxy<$F, $F::Model>,)+)> for ReturnTuple<($(FieldProxy<$F, $F::Model>,)+), $C> {
fn from(tuple: ($(FieldProxy<$F, $F::Model>,)+)) -> Self {
Self {
tuple,
columns: [$(
$F::NAME,
)+],
}
}
}
impl<M: Model, $($F: Field<Model = M>),+> Returning<M> for ReturnTuple<($(FieldProxy<$F, M>,)+), $C>
{
type Result = ($(
$F::Type,
)+);
fn decode(row: Row) -> Result<Self::Result, Error> {
Ok(($(
$F::Type::from_primitive(row.get($i)?),
)+))
}
fn columns(&self) -> &[&'static str] {
&self.columns
}
}
};
}
impl_select_tuple!(1, (A @ 0));
impl_select_tuple!(2, (A @ 0, B @ 1));
impl_select_tuple!(3, (A @ 0, B @ 1, C @ 2));
impl_select_tuple!(4, (A @ 0, B @ 1, C @ 2, D @ 3));
impl_select_tuple!(5, (A @ 0, B @ 1, C @ 2, D @ 3, E @ 4));
impl_select_tuple!(6, (A @ 0, B @ 1, C @ 2, D @ 3, E @ 4, F @ 5));
impl_select_tuple!(7, (A @ 0, B @ 1, C @ 2, D @ 3, E @ 4, F @ 5, G @ 6));
impl_select_tuple!(8, (A @ 0, B @ 1, C @ 2, D @ 3, E @ 4, F @ 5, G @ 6, H @ 7));