use toasty_core::{
schema::{app, mapping},
stmt,
};
use crate::engine::lower::LowerStatement;
struct FieldIncludes {
include_self: bool,
sub_paths: Vec<stmt::Projection>,
}
impl LowerStatement<'_, '_> {
pub(super) fn process_top_level_includes(
&mut self,
returning: &mut stmt::Expr,
include_paths: &[stmt::Path],
is_insert: bool,
) {
let stmt::Expr::Record(record) = returning else {
return;
};
let projections: Vec<stmt::Projection> = include_paths.iter().map(flatten_path).collect();
let app_fields = &self.model_unwrap().fields;
let mapping_fields = &self.mapping_unwrap().fields;
self.process_fields(record, app_fields, mapping_fields, &projections, is_insert);
}
fn process_fields(
&mut self,
returning: &mut stmt::ExprRecord,
app_fields: &[app::Field],
mapping_fields: &[mapping::Field],
include_paths: &[stmt::Projection],
is_insert: bool,
) {
for (i, (field, mapping)) in app_fields.iter().zip(mapping_fields).enumerate() {
let field_includes = partition_paths(include_paths, i);
if field.ty.is_relation() {
if field_includes.self_included() {
self.build_include_subquery(returning, i, &field_includes.sub_paths);
}
continue;
}
self.process_field(
&mut returning[i],
field,
mapping,
&field_includes,
is_insert,
);
}
}
fn process_field(
&mut self,
returning: &mut stmt::Expr,
field: &app::Field,
mapping: &mapping::Field,
matches: &FieldIncludes,
is_insert: bool,
) {
if field.deferred {
if !is_insert && !matches.self_included() {
return;
}
*returning = stmt::Expr::record([loaded_form(field, mapping)]);
if let app::FieldTy::Embedded(embedded) = &field.ty {
let stmt::Expr::Record(outer) = returning else {
unreachable!("just-wrapped record");
};
self.process_embed(
&mut outer[0],
embedded.target,
mapping,
&matches.sub_paths,
is_insert,
);
}
return;
}
if let app::FieldTy::Embedded(embedded) = &field.ty
&& (is_insert || !matches.sub_paths.is_empty())
{
self.process_embed(
returning,
embedded.target,
mapping,
&matches.sub_paths,
is_insert,
);
}
}
fn process_embed(
&mut self,
returning: &mut stmt::Expr,
target: app::ModelId,
mapping: &mapping::Field,
sub_paths: &[stmt::Projection],
is_insert: bool,
) {
match (self.schema().app.model(target), mapping) {
(app::Model::EmbeddedStruct(em), mapping::Field::Struct(fs)) => {
let stmt::Expr::Record(record) = returning else {
return;
};
self.process_fields(
record,
em.fields.as_slice(),
fs.fields.as_slice(),
sub_paths,
is_insert,
);
}
(app::Model::EmbeddedEnum(em), mapping::Field::Enum(fe)) => {
self.process_enum_arms(returning, em, fe, sub_paths, is_insert);
}
_ => {}
}
}
fn process_enum_arms(
&mut self,
returning: &mut stmt::Expr,
app_enum: &app::EmbeddedEnum,
mapping: &mapping::FieldEnum,
sub_paths: &[stmt::Projection],
is_insert: bool,
) {
let stmt::Expr::Match(match_expr) = returning else {
return;
};
for (variant_idx, arm) in match_expr.arms.iter_mut().enumerate() {
let variant_fields: Vec<&app::Field> = app_enum.variant_fields(variant_idx).collect();
if variant_fields.is_empty() {
continue;
}
let stmt::Expr::Record(arm_record) = &mut arm.expr else {
continue;
};
let variant_mapping = &mapping.variants[variant_idx];
let arm_sub_paths: Vec<stmt::Projection> = sub_paths
.iter()
.filter_map(|p| {
let (first, rest) = p.as_slice().split_first()?;
(*first == variant_idx).then(|| stmt::Projection::from(rest))
})
.collect();
for (j, (var_field, var_mapping)) in variant_fields
.iter()
.zip(&variant_mapping.fields)
.enumerate()
{
let field_includes = partition_paths(&arm_sub_paths, j);
self.process_field(
&mut arm_record[j + 1],
var_field,
var_mapping,
&field_includes,
is_insert,
);
}
}
}
fn build_include_subquery(
&mut self,
returning: &mut stmt::ExprRecord,
field_index: usize,
nested: &[stmt::Projection],
) {
returning[field_index] = self.build_relation_subquery(field_index, nested);
}
pub(super) fn build_relation_subquery(
&mut self,
field_index: usize,
nested: &[stmt::Projection],
) -> stmt::Expr {
let field = &self.model_unwrap().fields[field_index];
let (mut stmt, target_model_id) = match &field.ty {
app::FieldTy::HasMany(rel) => (
stmt::Query::new_select(
rel.target,
stmt::Expr::eq(
stmt::Expr::ref_parent_model(),
stmt::Expr::ref_self_field(rel.pair),
),
),
rel.target,
),
app::FieldTy::BelongsTo(rel) => {
let source_fk;
let target_pk;
if let [fk_field] = &rel.foreign_key.fields[..] {
source_fk = stmt::Expr::ref_parent_field(fk_field.source);
target_pk = stmt::Expr::ref_self_field(fk_field.target);
} else {
let mut source_fk_fields = vec![];
let mut target_pk_fields = vec![];
for fk_field in &rel.foreign_key.fields {
source_fk_fields.push(stmt::Expr::ref_parent_field(fk_field.source));
target_pk_fields.push(stmt::Expr::ref_parent_field(fk_field.source));
}
source_fk = stmt::Expr::record_from_vec(source_fk_fields);
target_pk = stmt::Expr::record_from_vec(target_pk_fields);
}
let mut query =
stmt::Query::new_select(rel.target, stmt::Expr::eq(source_fk, target_pk));
query.single = true;
(query, rel.target)
}
app::FieldTy::HasOne(rel) => {
let mut query = stmt::Query::new_select(
rel.target,
stmt::Expr::eq(
stmt::Expr::ref_parent_model(),
stmt::Expr::ref_self_field(rel.pair),
),
);
query.single = true;
(query, rel.target)
}
_ => unreachable!("build_include_subquery called on non-relation field"),
};
for rest in nested {
if !rest.is_empty() {
stmt.include(stmt::Path {
root: stmt::PathRoot::Model(target_model_id),
projection: rest.clone(),
});
}
}
let mut sub_expr = self.lower_sub_stmt(stmt::Statement::Query(stmt));
if field.nullable() && !field.ty.is_has_many() {
sub_expr = stmt::Expr::Let(stmt::ExprLet {
bindings: vec![sub_expr],
body: Box::new(stmt::Expr::match_expr(
stmt::Expr::arg(0),
vec![stmt::MatchArm {
pattern: stmt::Value::Null,
expr: stmt::Expr::from(0i64),
}],
stmt::Expr::arg(0),
)),
});
}
sub_expr
}
}
impl FieldIncludes {
fn self_included(&self) -> bool {
self.include_self || !self.sub_paths.is_empty()
}
}
fn partition_paths(paths: &[stmt::Projection], i: usize) -> FieldIncludes {
let mut include_self = false;
let mut sub_paths = Vec::new();
for path in paths {
if let Some((first, rest)) = path.as_slice().split_first()
&& *first == i
{
if rest.is_empty() {
include_self = true;
} else {
sub_paths.push(stmt::Projection::from(rest));
}
}
}
FieldIncludes {
include_self,
sub_paths,
}
}
fn flatten_path(path: &stmt::Path) -> stmt::Projection {
let mut acc = stmt::Projection::identity();
push_root_steps(&path.root, &mut acc);
for step in path.projection.as_slice() {
acc.push(*step);
}
acc
}
fn push_root_steps(root: &stmt::PathRoot, acc: &mut stmt::Projection) {
if let stmt::PathRoot::Variant { parent, variant_id } = root {
push_root_steps(&parent.root, acc);
for step in parent.projection.as_slice() {
acc.push(*step);
}
acc.push(variant_id.index);
}
}
fn loaded_form(field: &app::Field, mapping: &mapping::Field) -> stmt::Expr {
match (&field.ty, mapping) {
(app::FieldTy::Primitive(_), mapping::Field::Primitive(p)) => p.column_expr.clone(),
(app::FieldTy::Embedded(_), mapping::Field::Struct(s)) => s.default_returning.clone(),
(app::FieldTy::Embedded(_), mapping::Field::Enum(e)) => e.default_returning.clone(),
_ => unreachable!("deferred field has unexpected mapping shape"),
}
}