use crate::{
ActiveModelTrait, ColumnTrait, Condition, DbBackend, DbErr, EntityTrait, ExprTrait, IdenStatic,
Identity, ModelTrait, Value,
};
use sea_query::{ColumnRef, DynIden, Expr, IntoColumnRef, TableRef, ValueTuple};
use std::str::FromStr;
#[derive(Default)]
pub struct ValueTupleBuilder(Option<ValueTuple>);
impl ValueTupleBuilder {
pub fn push(&mut self, value: Value) {
match self.0.take() {
None => {
self.0 = Some(ValueTuple::One(value));
}
Some(ValueTuple::One(a)) => {
self.0 = Some(ValueTuple::Two(a, value));
}
Some(ValueTuple::Two(a, b)) => {
self.0 = Some(ValueTuple::Three(a, b, value));
}
Some(ValueTuple::Three(a, b, c)) => {
self.0 = Some(ValueTuple::Many(vec![a, b, c, value]));
}
Some(ValueTuple::Many(mut items)) => {
items.push(value);
self.0 = Some(ValueTuple::Many(items));
}
}
}
pub fn into_inner(self) -> Option<ValueTuple> {
self.0
}
}
pub fn get_key_from_model<Model>(columns: &Identity, model: &Model) -> Result<ValueTuple, DbErr>
where
Model: ModelTrait,
{
let mut values = ValueTupleBuilder::default();
for col in columns.iter() {
let col_name = col.inner();
let column = <<Model::Entity as EntityTrait>::Column as FromStr>::from_str(&col_name)
.map_err(|_| DbErr::Type(format!("Failed at mapping '{col_name}' to column")))?;
values.push(model.get(column));
}
match values.into_inner() {
Some(values) => Ok(values),
None => Err(DbErr::Type("Identity zero?".into())),
}
}
pub fn get_key_from_active_model<ActiveModel>(
columns: &Identity,
model: &ActiveModel,
) -> Result<ValueTuple, DbErr>
where
ActiveModel: ActiveModelTrait,
{
let mut values = ValueTupleBuilder::default();
for col in columns.iter() {
let col_name = col.inner();
let column = <<ActiveModel::Entity as EntityTrait>::Column as FromStr>::from_str(&col_name)
.map_err(|_| DbErr::Type(format!("Failed at mapping '{col_name}' to column")))?;
values.push(match model.get(column).into_value() {
Some(value) => value,
None => {
return Err(DbErr::AttrNotSet(format!(
"{}.{}",
<ActiveModel::Entity as Default>::default().as_str(),
col_name
)));
}
});
}
match values.into_inner() {
Some(values) => Ok(values),
None => Err(DbErr::Type("Identity zero?".into())),
}
}
pub fn set_key_on_active_model<ActiveModel>(
columns: &Identity,
model: &mut ActiveModel,
values: ValueTuple,
) -> Result<(), DbErr>
where
ActiveModel: ActiveModelTrait,
{
if values.arity() != columns.arity() {
return Err(DbErr::Type(format!(
"Arity mismatch: {} != {}",
values.arity(),
columns.arity(),
)));
}
for (column, value) in columns.iter().zip(values) {
let col_name = column.inner();
let column = <<ActiveModel::Entity as EntityTrait>::Column as FromStr>::from_str(&col_name)
.map_err(|_| DbErr::Type(format!("Failed at mapping '{col_name}' to column")))?;
model.set_if_not_equals(column, value);
}
Ok(())
}
pub fn clear_key_on_active_model<ActiveModel>(
columns: &Identity,
model: &mut ActiveModel,
) -> Result<bool, DbErr>
where
ActiveModel: ActiveModelTrait,
{
for col in columns.iter() {
let col_name = col.inner();
let column = <<ActiveModel::Entity as EntityTrait>::Column as FromStr>::from_str(&col_name)
.map_err(|_| DbErr::Type(format!("Failed at mapping '{col_name}' to column")))?;
if !column.def().is_null() {
return Ok(false);
}
model.set(
column,
match model.get(column).into_value() {
Some(value) => value.as_null(),
None => {
return Err(DbErr::AttrNotSet(format!(
"{}.{}",
<ActiveModel::Entity as Default>::default().as_str(),
col_name
)));
}
},
);
}
Ok(true)
}
pub fn column_tuple_in_condition(
table: &TableRef,
to: &Identity,
keys: &[ValueTuple],
backend: DbBackend,
) -> Result<Condition, DbErr> {
use itertools::Itertools;
let arity = to.arity();
let keys = keys.iter().unique();
if arity == 1 {
let values = keys
.map(|key| match key {
ValueTuple::One(v) => Ok(Expr::val(v.to_owned())),
_ => Err(arity_mismatch(arity, key)),
})
.collect::<Result<Vec<_>, DbErr>>()?;
let expr = Expr::col(table_column(
table,
to.iter().next().expect("Checked above"),
))
.is_in(values);
Ok(expr.into())
} else if cfg!(feature = "sqlite-no-row-value-before-3_15")
&& matches!(backend, DbBackend::Sqlite)
{
let table_columns = create_table_columns(table, to);
let mut outer = Condition::any();
for key in keys {
let key_arity = key.arity();
if arity != key_arity {
return Err(arity_mismatch(arity, key));
}
let table_columns = table_columns.iter().cloned();
let values = key.clone().into_iter().map(Expr::val);
let inner = table_columns
.zip(values)
.fold(Condition::all(), |cond, (column, value)| {
cond.add(column.eq(value))
});
outer = outer.add(inner);
}
Ok(outer)
} else {
let table_columns = create_table_columns(table, to);
let value_tuples = keys
.map(|key| {
let key_arity = key.arity();
if arity != key_arity {
return Err(arity_mismatch(arity, key));
}
let tuple_exprs = key.clone().into_iter().map(Expr::val);
Ok(Expr::tuple(tuple_exprs))
})
.collect::<Result<Vec<_>, DbErr>>()?;
let expr = Expr::tuple(table_columns).is_in(value_tuples);
Ok(expr.into())
}
}
fn arity_mismatch(expected: usize, actual: &ValueTuple) -> DbErr {
DbErr::Type(format!(
"Loader: arity mismatch: expected {expected}, got {} in {actual:?}",
actual.arity()
))
}
fn table_column(tbl: &TableRef, col: &DynIden) -> ColumnRef {
(tbl.sea_orm_table().to_owned(), col.clone()).into_column_ref()
}
fn create_table_columns(table: &TableRef, cols: &Identity) -> Vec<Expr> {
cols.iter()
.map(|col| table_column(table, col))
.map(Expr::col)
.collect()
}