use serde_json::Value as JsonValue;
use sqlx::Column as _;
use sqlx::Row as _;
use crate::orm::{HydrateRelated, Model};
pub(super) fn joined_pk_is_null(
row: &sqlx::sqlite::SqliteRow,
pk_col: &crate::migrate::Column,
alias: &str,
) -> bool {
let mut as_nullable = pk_col.clone();
as_nullable.nullable = true;
crate::orm::dynamic::decode_to_json_aliased(row, &as_nullable, alias)
.map(|v| v.is_null())
.unwrap_or(true)
}
pub(super) fn row_to_json(row: &sqlx::sqlite::SqliteRow) -> JsonValue {
let mut map = serde_json::Map::new();
for col in row.columns() {
let name = col.name().to_string();
let ord = col.ordinal();
let val: JsonValue = if let Ok(opt) = row.try_get::<Option<i64>, _>(ord) {
opt.map_or(JsonValue::Null, |v| JsonValue::Number(v.into()))
} else if let Ok(opt) = row.try_get::<Option<f64>, _>(ord) {
opt.map_or(JsonValue::Null, |v| serde_json::json!(v))
} else if let Ok(opt) = row.try_get::<Option<bool>, _>(ord) {
opt.map_or(JsonValue::Null, JsonValue::Bool)
} else if let Ok(opt) = row.try_get::<Option<String>, _>(ord) {
opt.map_or(JsonValue::Null, JsonValue::String)
} else {
JsonValue::Null
};
map.insert(name, val);
}
JsonValue::Object(map)
}
pub(super) fn hydrate_joined_rels<T: Model + HydrateRelated>(
t: &mut T,
row: &sqlx::sqlite::SqliteRow,
join_fields: &[String],
) -> Result<(), sqlx::Error> {
let registered = crate::migrate::registered_models();
for field_name in join_fields {
let Some(hops) = crate::orm::queryset::resolve_join_hops_for::<T>(field_name) else {
continue;
};
let segs: Vec<&str> = field_name.split("__").collect();
let mut deeper: Option<serde_json::Value> = None;
let mut hop0_missing = false;
for idx in (0..hops.len()).rev() {
let hop = &hops[idx];
let prefix = segs[..=idx].join("__");
let Some(meta) = registered.iter().find(|m| m.table == hop.child_table) else {
deeper = None;
if idx == 0 {
hop0_missing = true;
}
continue;
};
let Some(pk_col) = meta.fields.iter().find(|c| c.primary_key) else {
deeper = None;
if idx == 0 {
hop0_missing = true;
}
continue;
};
let pk_alias = format!("{prefix}__{}", pk_col.name);
let pk_is_null = joined_pk_is_null(row, pk_col, &pk_alias);
if pk_is_null {
deeper = None;
if idx == 0 {
hop0_missing = true;
}
continue;
}
let mut obj = serde_json::Map::with_capacity(meta.fields.len());
for col in &meta.fields {
let alias = format!("{prefix}__{}", col.name);
let val = crate::orm::dynamic::decode_to_json_aliased(row, col, &alias)?;
obj.insert(col.name.clone(), val);
}
if let Some(child) = deeper.take()
&& let Some(next_seg) = segs.get(idx + 1)
{
obj.insert((*next_seg).to_string(), child);
}
deeper = Some(serde_json::Value::Object(obj));
}
if hop0_missing {
continue;
}
if let Some(nested) = deeper {
t.hydrate_fk(segs[0], &nested);
}
}
Ok(())
}
pub(super) fn extract_m2m_child_json<T: Model>(
row: &sqlx::sqlite::SqliteRow,
field_name: &str,
child_meta: &crate::migrate::ModelMeta,
) -> Result<Option<JsonValue>, sqlx::Error> {
let segs: Vec<&str> = field_name.split("__").collect();
let m2m_seg = segs[0];
let Some(pk_col) = child_meta.fields.iter().find(|c| c.primary_key) else {
return Ok(None);
};
let pk_alias = format!("{m2m_seg}__{}", pk_col.name);
let pk_null = joined_pk_is_null(row, pk_col, &pk_alias);
if pk_null {
return Ok(None);
}
let mut obj = serde_json::Map::with_capacity(child_meta.fields.len());
for col in &child_meta.fields {
let alias = format!("{m2m_seg}__{}", col.name);
let val = crate::orm::dynamic::decode_to_json_aliased(row, col, &alias)?;
obj.insert(col.name.clone(), val);
}
if let Some((_ct, _cpk, onward)) = crate::orm::queryset::resolve_m2m_chain::<T>(field_name) {
let registered = crate::migrate::registered_models();
let mut deeper: Option<JsonValue> = None;
for i in (0..onward.len()).rev() {
let hop = &onward[i];
let seg_idx = i + 1;
let prefix = segs[..=seg_idx].join("__");
let Some(meta) = registered.iter().find(|m| m.table == hop.child_table) else {
deeper = None;
continue;
};
let Some(hpk) = meta.fields.iter().find(|c| c.primary_key) else {
deeper = None;
continue;
};
let hpk_alias = format!("{prefix}__{}", hpk.name);
let hpk_null = joined_pk_is_null(row, hpk, &hpk_alias);
if hpk_null {
deeper = None;
continue;
}
let mut hobj = serde_json::Map::with_capacity(meta.fields.len());
for col in &meta.fields {
let alias = format!("{prefix}__{}", col.name);
let val = crate::orm::dynamic::decode_to_json_aliased(row, col, &alias)?;
hobj.insert(col.name.clone(), val);
}
if let Some(child) = deeper.take()
&& let Some(next_seg) = segs.get(seg_idx + 1)
{
hobj.insert((*next_seg).to_string(), child);
}
deeper = Some(JsonValue::Object(hobj));
}
if let Some(top) = deeper
&& let Some(first_onward_seg) = segs.get(1)
{
obj.insert((*first_onward_seg).to_string(), top);
}
}
Ok(Some(JsonValue::Object(obj)))
}
pub(super) fn pk_to_json(
row: &sqlx::sqlite::SqliteRow,
col_name: &str,
ty: crate::orm::SqlType,
) -> Result<JsonValue, sqlx::Error> {
use crate::orm::SqlType::*;
use serde_json::json;
Ok(match ty {
SmallInt | Integer | BigInt | ForeignKey => json!(row.try_get::<i64, _>(col_name)?),
Text => json!(row.try_get::<String, _>(col_name)?),
Uuid => json!(row.try_get::<uuid::Uuid, _>(col_name)?.to_string()),
_ => JsonValue::Null,
})
}
pub(super) fn decode_agg(
row: &sqlx::sqlite::SqliteRow,
name: &str,
agg: &crate::orm::Aggregate,
source_ty: Option<crate::orm::SqlType>,
) -> Result<JsonValue, sqlx::Error> {
use crate::orm::SqlType::*;
use crate::orm::aggregate::AggregateKind;
use serde_json::json;
Ok(match agg.kind() {
AggregateKind::Count => json!(row.try_get::<i64, _>(name)?),
AggregateKind::Avg => row
.try_get::<Option<f64>, _>(name)?
.map_or(JsonValue::Null, |f| json!(f)),
AggregateKind::Sum | AggregateKind::Max | AggregateKind::Min => match source_ty {
Some(SmallInt | Integer | BigInt | ForeignKey) => row
.try_get::<Option<i64>, _>(name)?
.map_or(JsonValue::Null, |n| json!(n)),
Some(Real | Double) => row
.try_get::<Option<f64>, _>(name)?
.map_or(JsonValue::Null, |f| json!(f)),
_ => row
.try_get::<Option<String>, _>(name)?
.map_or(JsonValue::Null, JsonValue::String),
},
})
}