use toasty_core::{
schema::app::{self, BelongsTo, FieldTy, VariantId},
stmt::{Expr, ExprContext, ExprReference, ResolvedRef},
};
pub(super) struct ResolvedRelation<'a> {
pub(super) field: &'a app::Field,
pub(super) target: app::ModelId,
embedded: Option<EmbeddedRelation<'a>>,
target_expr: Option<Expr>,
}
struct EmbeddedRelation<'a> {
belongs_to: &'a BelongsTo,
key_expr: Expr,
}
impl<'a> ResolvedRelation<'a> {
fn new(field: &'a app::Field, embedded: Option<EmbeddedRelation<'a>>) -> Option<Self> {
let target = field.relation_target_id()?;
Some(Self {
field,
target,
embedded,
target_expr: None,
})
}
fn project(&mut self, indices: &[usize]) {
let Some((index, remaining)) = indices.split_first() else {
return;
};
self.target_expr = Some(match self.target_expr.take() {
Some(base) => project(base, indices),
None => project(Expr::ref_self_field(self.target.field(*index)), remaining),
});
}
pub(super) fn is_endpoint(&self) -> bool {
self.target_expr.is_none()
}
pub(super) fn embedded(&self) -> Option<&'a BelongsTo> {
self.embedded.as_ref().map(|relation| relation.belongs_to)
}
pub(super) fn key_expr(&self) -> Option<Expr> {
self.embedded
.as_ref()
.map(|relation| relation.key_expr.clone())
}
pub(super) fn target_expr(&self) -> Option<Expr> {
self.target_expr.clone()
}
}
pub(super) fn resolve<'a>(cx: &ExprContext<'a>, expr: &Expr) -> Option<ResolvedRelation<'a>> {
match resolve_expr(cx, expr)? {
PathTarget::Relation(relation) => Some(relation),
PathTarget::Embed(_) => None,
}
}
enum PathTarget<'a> {
Embed(EmbedTarget<'a>),
Relation(ResolvedRelation<'a>),
}
fn resolve_expr<'a>(cx: &ExprContext<'a>, expr: &Expr) -> Option<PathTarget<'a>> {
match expr {
Expr::Reference(reference @ ExprReference::Field { .. }) => {
let ResolvedRef::Field(field) = cx.resolve_expr_reference(reference) else {
return None;
};
match &field.ty {
FieldTy::Embedded(embedded) => Some(PathTarget::Embed(EmbedTarget::embed(
&cx.schema().app,
embedded.target,
)?)),
_ => Some(PathTarget::Relation(ResolvedRelation::new(field, None)?)),
}
}
Expr::Project(projection) => {
let indices = projection.projection.as_slice();
let mut target = match resolve_expr(cx, &projection.base)? {
PathTarget::Relation(mut relation) => {
relation.project(indices);
return Some(PathTarget::Relation(relation));
}
PathTarget::Embed(target) => target,
};
for (position, index) in indices.iter().enumerate() {
let field = target.field_at(*index)?;
match &field.ty {
FieldTy::BelongsTo(belongs_to) => {
let host = project(copy_path(&projection.base), &indices[..position]);
let key_expr = embedded_key_expr(host, target, belongs_to)?;
let mut relation = ResolvedRelation::new(
field,
Some(EmbeddedRelation {
belongs_to,
key_expr,
}),
)?;
relation.project(&indices[position + 1..]);
return Some(PathTarget::Relation(relation));
}
FieldTy::Embedded(embedded) => {
target = EmbedTarget::embed(&cx.schema().app, embedded.target)?;
}
_ => return None,
}
}
Some(PathTarget::Embed(target))
}
Expr::Variant(variant) => match resolve_expr(cx, &variant.base)? {
PathTarget::Embed(target) => Some(PathTarget::Embed(target.select(variant.variant)?)),
PathTarget::Relation(mut relation) => {
relation.target_expr =
Some(Expr::variant(relation.target_expr.take()?, variant.variant));
Some(PathTarget::Relation(relation))
}
},
_ => None,
}
}
fn embedded_key_expr(host: Expr, target: EmbedTarget<'_>, belongs_to: &BelongsTo) -> Option<Expr> {
let mut fields = Vec::with_capacity(belongs_to.foreign_key.fields.len());
for foreign_key in &belongs_to.foreign_key.fields {
let index = target.step_of(foreign_key.source.index)?;
fields.push(Expr::project(host.clone(), [index]));
}
Some(super::scalar_or_record(fields.into_iter()))
}
fn copy_path(expr: &Expr) -> Expr {
match expr {
Expr::Project(projection) => project(
copy_path(&projection.base),
projection.projection.as_slice(),
),
Expr::Variant(variant) => Expr::variant(copy_path(&variant.base), variant.variant),
_ => expr.clone(),
}
}
fn project(mut base: Expr, indices: &[usize]) -> Expr {
if let Expr::Project(project) = &mut base {
for index in indices {
project.projection.push(*index);
}
base
} else if indices.is_empty() {
base
} else {
Expr::project(base, indices)
}
}
#[derive(Clone, Copy)]
pub(crate) enum EmbedTarget<'a> {
Struct(&'a app::EmbeddedStruct),
Enum(&'a app::EmbeddedEnum, Option<VariantId>),
}
impl<'a> EmbedTarget<'a> {
pub(crate) fn embed(schema: &'a app::Schema, model_id: app::ModelId) -> Option<Self> {
match schema.model(model_id) {
app::Model::EmbeddedStruct(embedded) => Some(EmbedTarget::Struct(embedded)),
app::Model::EmbeddedEnum(embedded) => Some(EmbedTarget::Enum(embedded, None)),
app::Model::Root(_) => None,
}
}
pub(crate) fn select(self, variant: VariantId) -> Option<Self> {
match self {
EmbedTarget::Enum(embedded, None)
if embedded.id == variant.model && variant.index < embedded.variants.len() =>
{
Some(EmbedTarget::Enum(embedded, Some(variant)))
}
_ => None,
}
}
pub(crate) fn field_at(&self, index: usize) -> Option<&'a app::Field> {
match self {
EmbedTarget::Struct(embedded) => embedded.fields.get(index),
EmbedTarget::Enum(embedded, Some(variant)) => {
embedded.variant_fields(variant.index).get(index)
}
EmbedTarget::Enum(_, None) => None,
}
}
fn step_of(&self, field_index: usize) -> Option<usize> {
match self {
EmbedTarget::Struct(_) => Some(field_index),
EmbedTarget::Enum(embedded, Some(variant)) => embedded
.variant_fields(variant.index)
.iter()
.position(|field| field.id.index == field_index),
EmbedTarget::Enum(_, None) => None,
}
}
}