use sea_query::{Alias, Query};
use crate::orm::Model;
pub(crate) fn fk_pk_hint(field: &crate::orm::FieldSpec) -> Option<crate::orm::SqlType> {
field
.fk_target
.and_then(|t| crate::migrate::pk_meta_for_table(t).map(|(_, ty)| ty))
}
fn reject_unset_fk_placeholder(
field: &crate::orm::FieldSpec,
val: &serde_json::Value,
) -> Result<(), crate::orm::write::WriteError> {
let is_unset_fk = field.fk_target.is_some()
&& !field.nullable
&& !field.primary_key
&& val
.as_i64()
.map(|n| n == 0)
.or_else(|| val.as_u64().map(|n| n == 0))
.unwrap_or(false);
if is_unset_fk {
let target = field.fk_target.unwrap_or("the target");
return Err(crate::orm::write::WriteError::Validator {
field: field.name.to_string(),
message: format!(
"foreign key `{}` is the unset id-0 placeholder — set it to a real {} id before create",
field.name, target
),
});
}
Ok(())
}
pub(super) fn serialize_to_map<T: serde::Serialize>(
instance: &T,
) -> Result<serde_json::Map<String, serde_json::Value>, crate::orm::write::WriteError> {
let value = serde_json::to_value(instance)?;
match value {
serde_json::Value::Object(map) => Ok(map),
_ => Err(crate::orm::write::WriteError::NotAnObject),
}
}
pub(super) fn build_insert_one_for<T: Model>(
_backend_name: &str,
map: &serde_json::Map<String, serde_json::Value>,
) -> Result<sea_query::InsertStatement, crate::orm::write::WriteError> {
use crate::orm::write::{is_default_pk, json_to_sea_value, now_for_column, user_for_column};
let mut columns: Vec<Alias> = Vec::new();
let mut values: Vec<sea_query::SimpleExpr> = Vec::new();
for field in T::FIELDS {
let val = map
.get(field.name)
.cloned()
.unwrap_or(serde_json::Value::Null);
if field.primary_key && is_default_pk(field.ty, &val) {
continue;
}
if field.auto_now_add || field.auto_now {
columns.push(Alias::new(field.name));
values.push(now_for_column(field.ty).into());
continue;
}
if field.auto_user_add || field.auto_user {
columns.push(Alias::new(field.name));
values.push(user_for_column(field.ty).into());
continue;
}
if val.is_null() && field.nullable && !map.contains_key(field.name) {
continue;
}
let val =
crate::orm::write::normalize_json(field.trim, field.lowercase, &val).unwrap_or(val);
let val = crate::orm::cleaners::apply(T::TABLE, field.name, &val)?.unwrap_or(val);
reject_unset_fk_placeholder(field, &val)?;
let sea_value = json_to_sea_value(
field.ty,
&val,
field.nullable,
field.name,
fk_pk_hint(field),
)?;
columns.push(Alias::new(field.name));
values.push(sea_value.into());
}
let mut stmt = Query::insert();
stmt.into_table(crate::db::router::schema_qualified_table(T::TABLE))
.columns(columns);
stmt.values(values).map_err(|e| {
crate::orm::write::WriteError::Sqlx(sqlx::Error::Protocol(format!(
"umbral::orm::write: sea-query rejected INSERT values: {e}"
)))
})?;
stmt.returning_all();
Ok(stmt)
}
pub(super) fn build_insert_many_for<T: Model>(
_backend_name: &str,
maps: &[serde_json::Map<String, serde_json::Value>],
) -> Result<sea_query::InsertStatement, crate::orm::write::WriteError> {
use crate::orm::write::{is_default_pk, json_to_sea_value, now_for_column};
let first = &maps[0];
let included_fields: Vec<&crate::orm::FieldSpec> = T::FIELDS
.iter()
.filter(|field| {
let val = first
.get(field.name)
.cloned()
.unwrap_or(serde_json::Value::Null);
if field.primary_key && is_default_pk(field.ty, &val) {
return false;
}
if val.is_null() && field.nullable && !first.contains_key(field.name) {
return false;
}
true
})
.collect();
let columns: Vec<Alias> = included_fields.iter().map(|f| Alias::new(f.name)).collect();
let mut stmt = Query::insert();
stmt.into_table(crate::db::router::schema_qualified_table(T::TABLE))
.columns(columns);
for map in maps {
let row_values: Result<Vec<_>, _> = included_fields
.iter()
.map(|field| {
if field.auto_now_add || field.auto_now {
return Ok(sea_query::SimpleExpr::from(now_for_column(field.ty)));
}
let val = map
.get(field.name)
.cloned()
.unwrap_or(serde_json::Value::Null);
let val = crate::orm::write::normalize_json(field.trim, field.lowercase, &val)
.unwrap_or(val);
let val = crate::orm::cleaners::apply(T::TABLE, field.name, &val)?.unwrap_or(val);
reject_unset_fk_placeholder(field, &val)?;
json_to_sea_value(
field.ty,
&val,
field.nullable,
field.name,
fk_pk_hint(field),
)
.map(sea_query::SimpleExpr::from)
})
.collect();
stmt.values(row_values?).map_err(|e| {
crate::orm::write::WriteError::Sqlx(sqlx::Error::Protocol(format!(
"umbral::orm::write: sea-query rejected INSERT values: {e}"
)))
})?;
}
Ok(stmt)
}
pub(super) fn pk_field<T: Model>() -> Option<&'static crate::orm::FieldSpec> {
T::FIELDS.iter().find(|f| f.primary_key)
}