1use super::{EnumVariant, Field, FieldId, FieldPrimitive, FieldTy, Model, ModelId, VariantId};
2
3use crate::{Result, stmt};
4use indexmap::IndexMap;
5use std::collections::HashSet;
6
7#[derive(Debug)]
26pub enum Resolved<'a> {
27 Field(&'a Field),
29 Variant(&'a EnumVariant),
31}
32
33#[derive(Debug, Default)]
50pub struct Schema {
51 pub models: IndexMap<ModelId, Model>,
53}
54
55#[derive(Default)]
56struct Builder {
57 models: IndexMap<ModelId, Model>,
58}
59
60impl Schema {
61 pub fn from_macro(models: impl IntoIterator<Item = Model>) -> Result<Self> {
66 Builder::from_macro(models)
67 }
68
69 pub fn field(&self, id: FieldId) -> &Field {
75 self.model(id.model)
76 .fields()
77 .get(id.index)
78 .expect("invalid field ID")
79 }
80
81 pub fn variant(&self, id: VariantId) -> &EnumVariant {
88 let Model::EmbeddedEnum(e) = self.model(id.model) else {
89 panic!("VariantId references a non-enum model");
90 };
91 e.variants.get(id.index).expect("invalid variant index")
92 }
93
94 pub fn models(&self) -> impl Iterator<Item = &Model> {
96 self.models.values()
97 }
98
99 pub fn get_model(&self, id: impl Into<ModelId>) -> Option<&Model> {
101 self.models.get(&id.into())
102 }
103
104 pub fn model(&self, id: impl Into<ModelId>) -> &Model {
110 self.models.get(&id.into()).expect("invalid model ID")
111 }
112
113 pub fn fields(&self, id: impl Into<ModelId>) -> &[Field] {
124 self.model(id).fields()
125 }
126
127 pub fn project_fields<'s>(
144 &'s self,
145 model: ModelId,
146 projection: &[usize],
147 ) -> impl Iterator<Item = &'s Field> {
148 let mut current = Some(model);
149 let mut steps = projection.iter();
150
151 std::iter::from_fn(move || {
152 let &index = steps.next()?;
153 let field = self.get_model(current?)?.fields().get(index)?;
154 current = match field.expr_ty() {
155 stmt::Type::Model(nested) => Some(*nested),
156 _ => None,
157 };
158 Some(field)
159 })
160 }
161
162 pub fn resolve<'a>(
174 &'a self,
175 root: &'a Model,
176 projection: &stmt::Projection,
177 ) -> Option<Resolved<'a>> {
178 let [first, rest @ ..] = projection.as_slice() else {
179 return None;
180 };
181
182 let mut current_field = root.as_root_unwrap().fields.get(*first)?;
184
185 let mut steps = rest.iter();
188 while let Some(step) = steps.next() {
189 match ¤t_field.ty {
190 FieldTy::Primitive(FieldPrimitive {
197 ty: stmt::Type::Model(embed_id),
198 ..
199 }) => {
200 let consumed = rest.len() - steps.as_slice().len();
205 let doc_path = &rest[consumed - 1..];
206 return (self.project_fields(*embed_id, doc_path).count() == doc_path.len())
207 .then_some(Resolved::Field(current_field));
208 }
209 FieldTy::Primitive(..) => {
210 return None;
212 }
213 FieldTy::Embedded(embedded) => {
214 let target = self.model(embedded.target);
215 match target {
216 Model::EmbeddedStruct(s) => {
217 current_field = s.fields.get(*step)?;
218 }
219 Model::EmbeddedEnum(e) => {
220 let variant = e.variants.get(*step)?;
221
222 if let Some(field_step) = steps.next() {
224 current_field = e.fields.get(*field_step)?;
226 } else {
227 return Some(Resolved::Variant(variant));
229 }
230 }
231 _ => return None,
232 }
233 }
234 FieldTy::BelongsTo(belongs_to) => {
235 current_field = belongs_to.target(self).as_root_unwrap().fields.get(*step)?;
236 }
237 FieldTy::Has(has) => {
238 current_field = has.target(self).as_root_unwrap().fields.get(*step)?;
239 }
240 FieldTy::Via(via) => {
241 current_field = via.target(self).as_root_unwrap().fields.get(*step)?;
242 }
243 };
244 }
245
246 Some(Resolved::Field(current_field))
247 }
248
249 pub fn resolve_field<'a>(
254 &'a self,
255 root: &'a Model,
256 projection: &stmt::Projection,
257 ) -> Option<&'a Field> {
258 match self.resolve(root, projection) {
259 Some(Resolved::Field(field)) => Some(field),
260 _ => None,
261 }
262 }
263
264 pub fn resolve_field_path<'a>(&'a self, path: &stmt::Path) -> Option<&'a Field> {
267 let model = self.model(path.root.as_model_unwrap());
268 self.resolve_field(model, &path.projection)
269 }
270}
271
272impl Builder {
273 pub(crate) fn from_macro(models: impl IntoIterator<Item = Model>) -> Result<Schema> {
274 let mut builder = Self { ..Self::default() };
275
276 for model in models {
277 builder.models.insert(model.id(), model);
278 }
279
280 builder.process_models()?;
281 builder.into_schema()
282 }
283
284 fn into_schema(self) -> Result<Schema> {
285 Ok(Schema {
286 models: self.models,
287 })
288 }
289
290 fn process_models(&mut self) -> Result<()> {
291 self.link_relations()?;
294 self.resolve_via_targets()?;
295 self.verify_no_eager_load_cycles()?;
296
297 Ok(())
298 }
299
300 fn resolve_via_targets(&mut self) -> crate::Result<()> {
309 let mut updates = Vec::new();
311
312 for curr in 0..self.models.len() {
313 if self.models[curr].is_embedded() {
314 continue;
315 }
316 let src = self.models[curr].id();
317 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
318 let field = &self.models[curr].as_root_unwrap().fields[index];
319 let FieldTy::Via(via) = &field.ty else {
320 continue;
321 };
322 let Some(terminal) = via.terminal else {
323 continue;
324 };
325
326 let projection = via.path.projection.as_slice();
328 let relation_steps = &projection[..projection.len() - 1];
329 let field_name = field.name.app_unwrap().to_string();
330 let target = self.walk_via_relation_chain(src, relation_steps, &field_name)?;
331
332 let terminal_field = &self.models[&target].as_root_unwrap().fields[terminal];
334 if !matches!(terminal_field.ty, FieldTy::Primitive(_)) {
335 return Err(crate::Error::invalid_schema(format!(
336 "the `via` terminal `{}::{}` is not a scalar field",
337 self.models[&target].name().upper_camel_case(),
338 terminal_field.name.app_unwrap(),
339 )));
340 }
341
342 updates.push((curr, index, target));
343 }
344 }
345
346 for (curr, index, target) in updates {
347 if let FieldTy::Via(via) = &mut self.models[curr].as_root_mut_unwrap().fields[index].ty
348 {
349 via.target = target;
350 }
351 }
352
353 Ok(())
354 }
355
356 fn walk_via_relation_chain(
359 &self,
360 declaring: ModelId,
361 steps: &[usize],
362 field_name: &str,
363 ) -> crate::Result<ModelId> {
364 let mut current = declaring;
365 let mut queue: Vec<usize> = steps.iter().rev().copied().collect();
366
367 while let Some(idx) = queue.pop() {
368 let field = &self.models[¤t].as_root_unwrap().fields[idx];
369 match &field.ty {
370 FieldTy::Has(has) => current = has.target,
371 FieldTy::BelongsTo(belongs_to) => current = belongs_to.target,
372 FieldTy::Via(inner) => {
375 let inner_projection = inner.path.projection.as_slice();
376 let inner_steps = match inner.terminal {
377 Some(_) => &inner_projection[..inner_projection.len() - 1],
378 None => inner_projection,
379 };
380 for step in inner_steps.iter().rev() {
381 queue.push(*step);
382 }
383 }
384 _ => {
385 return Err(crate::Error::invalid_schema(format!(
386 "the `via` path for `{}::{}` traverses `{}`, which is not a relation",
387 self.models[&declaring].name().upper_camel_case(),
388 field_name,
389 field.name.app_unwrap(),
390 )));
391 }
392 }
393 }
394
395 Ok(current)
396 }
397
398 fn verify_no_eager_load_cycles(&self) -> crate::Result<()> {
399 let mut visited = HashSet::new();
400 let mut model_stack = Vec::new();
401 let mut field_stack = Vec::new();
402
403 for model in self.models.values() {
404 if model.is_embedded() {
405 continue;
406 }
407 self.visit_eager_load_graph(
408 model.id(),
409 &mut visited,
410 &mut model_stack,
411 &mut field_stack,
412 )?;
413 }
414
415 Ok(())
416 }
417
418 fn visit_eager_load_graph(
419 &self,
420 model_id: ModelId,
421 visited: &mut HashSet<ModelId>,
422 model_stack: &mut Vec<ModelId>,
423 field_stack: &mut Vec<FieldId>,
424 ) -> crate::Result<()> {
425 if model_stack.contains(&model_id) {
426 return Ok(());
427 }
428
429 if !visited.insert(model_id) {
430 return Ok(());
431 }
432
433 model_stack.push(model_id);
434
435 let model = self.models[&model_id].as_root_unwrap();
436 for field in &model.fields {
437 let Some(target) = eager_relation_target(field) else {
438 continue;
439 };
440
441 if let Some(pos) = model_stack.iter().position(|id| *id == target) {
442 let mut cycle = field_stack[pos..].to_vec();
443 cycle.push(field.id);
444 return Err(crate::Error::invalid_schema(format!(
445 "eager relation cycle detected: {}",
446 self.format_eager_load_cycle(&cycle, target)
447 )));
448 }
449
450 field_stack.push(field.id);
451 self.visit_eager_load_graph(target, visited, model_stack, field_stack)?;
452 field_stack.pop();
453 }
454
455 model_stack.pop();
456 Ok(())
457 }
458
459 fn format_eager_load_cycle(&self, fields: &[FieldId], target: ModelId) -> String {
460 let mut parts = Vec::new();
461 for field_id in fields {
462 let model = &self.models[&field_id.model];
463 let field = &model.as_root_unwrap().fields[field_id.index];
464 parts.push(format!(
465 "{}::{}",
466 model.name().upper_camel_case(),
467 field.name.app_unwrap()
468 ));
469 }
470 parts.push(self.models[&target].name().upper_camel_case());
471 parts.join(" -> ")
472 }
473
474 fn link_relations(&mut self) -> crate::Result<()> {
476 for curr in 0..self.models.len() {
484 if self.models[curr].is_embedded() {
485 continue;
486 }
487 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
488 let model = &self.models[curr];
489 let src = model.id();
490 let field = &model.as_root_unwrap().fields[index];
491
492 if let FieldTy::Has(has) = &field.ty
493 && has.is_many()
494 {
495 let target = has.target;
496 let field_name = field.name.app_unwrap().to_string();
497 let pair = if has.pair_id.is_placeholder() {
498 self.find_has_many_pair(src, target, &field_name)?
499 } else {
500 self.validate_pair(src, target, &field_name, has.pair_id)?;
501 has.pair_id
502 };
503 self.models[curr].as_root_mut_unwrap().fields[index]
504 .ty
505 .as_has_mut_unwrap()
506 .pair_id = pair;
507 }
508 }
509 }
510
511 for curr in 0..self.models.len() {
513 if self.models[curr].is_embedded() {
514 continue;
515 }
516 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
517 let model = &self.models[curr];
518 let src = model.id();
519 let field = &model.as_root_unwrap().fields[index];
520
521 match &field.ty {
522 FieldTy::Has(has) if has.is_one() => {
523 let target = has.target;
524 let field_name = field.name.app_unwrap().to_string();
525 let pair = if has.pair_id.is_placeholder() {
526 match self.find_belongs_to_pair(src, target, &field_name)? {
527 Some(pair) => pair,
528 None => {
529 return Err(crate::Error::invalid_schema(format!(
530 "field `{}::{}` has no matching `BelongsTo` relation on the target model",
531 self.models[curr].name().upper_camel_case(),
532 field_name,
533 )));
534 }
535 }
536 } else {
537 self.validate_pair(src, target, &field_name, has.pair_id)?;
538 has.pair_id
539 };
540
541 self.models[curr].as_root_mut_unwrap().fields[index]
542 .ty
543 .as_has_mut_unwrap()
544 .pair_id = pair;
545 }
546 FieldTy::BelongsTo(belongs_to) => {
547 assert!(!belongs_to.foreign_key.is_placeholder());
548 continue;
549 }
550 _ => {}
551 }
552 }
553 }
554
555 for curr in 0..self.models.len() {
557 if self.models[curr].is_embedded() {
558 continue;
559 }
560 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
561 let model = &self.models[curr];
562 let field_id = model.as_root_unwrap().fields[index].id;
563
564 let pair = match &self.models[curr].as_root_unwrap().fields[index].ty {
565 FieldTy::BelongsTo(belongs_to) => {
566 let mut pair = None;
567 let target = match self.models.get_index_of(&belongs_to.target) {
568 Some(target) => target,
569 None => {
570 let model = &self.models[curr];
571 return Err(crate::Error::invalid_schema(format!(
572 "field `{}::{}` references a model that was not registered \
573 with the schema; did you forget to register it with `Db::builder()`?",
574 model.name().upper_camel_case(),
575 model.as_root_unwrap().fields[index].name(),
576 )));
577 }
578 };
579
580 for target_index in 0..self.models[target].as_root_unwrap().fields.len() {
581 pair = match &self.models[target].as_root_unwrap().fields[target_index]
582 .ty
583 {
584 FieldTy::Has(has) if has.pair_id == field_id => {
585 assert!(pair.is_none());
586 Some(
587 self.models[target].as_root_unwrap().fields[target_index]
588 .id,
589 )
590 }
591 _ => continue,
592 }
593 }
594
595 if pair.is_none() {
596 continue;
597 }
598
599 pair
600 }
601 _ => continue,
602 };
603
604 self.models[curr].as_root_mut_unwrap().fields[index]
605 .ty
606 .as_belongs_to_mut_unwrap()
607 .pair = pair;
608 }
609 }
610
611 Ok(())
612 }
613
614 fn find_belongs_to_pair(
615 &self,
616 src: ModelId,
617 target: ModelId,
618 field_name: &str,
619 ) -> crate::Result<Option<FieldId>> {
620 let src_model = &self.models[&src];
621
622 let target = match self.models.get(&target) {
623 Some(target) => target,
624 None => {
625 return Err(crate::Error::invalid_schema(format!(
626 "field `{}::{}` references a model that was not registered with the schema; \
627 did you forget to register it with `Db::builder()`?",
628 src_model.name().upper_camel_case(),
629 field_name,
630 )));
631 }
632 };
633
634 let belongs_to: Vec<_> = target
636 .as_root_unwrap()
637 .fields
638 .iter()
639 .filter(|field| match &field.ty {
640 FieldTy::BelongsTo(rel) => rel.target == src,
641 _ => false,
642 })
643 .collect();
644
645 match &belongs_to[..] {
646 [field] => Ok(Some(field.id)),
647 [] => Ok(None),
648 _ => Err(crate::Error::invalid_schema(format!(
649 "model `{}` has more than one `BelongsTo` relation targeting `{}`; \
650 disambiguate by adding `pair = <field>` on the paired `has_many`/`has_one` \
651 field",
652 target.name().upper_camel_case(),
653 src_model.name().upper_camel_case(),
654 ))),
655 }
656 }
657
658 fn find_has_many_pair(
659 &mut self,
660 src: ModelId,
661 target: ModelId,
662 field_name: &str,
663 ) -> crate::Result<FieldId> {
664 if let Some(field_id) = self.find_belongs_to_pair(src, target, field_name)? {
665 return Ok(field_id);
666 }
667
668 Err(crate::Error::invalid_schema(format!(
669 "field `{}::{}` has no matching `BelongsTo` relation on the target model",
670 self.models[&src].name().upper_camel_case(),
671 field_name,
672 )))
673 }
674
675 fn validate_pair(
679 &self,
680 src: ModelId,
681 target: ModelId,
682 field_name: &str,
683 pair: FieldId,
684 ) -> crate::Result<()> {
685 let src_model = &self.models[&src];
686
687 let target_model = match self.models.get(&target) {
688 Some(target) => target,
689 None => {
690 return Err(crate::Error::invalid_schema(format!(
691 "field `{}::{}` references a model that was not registered with the schema; \
692 did you forget to register it with `Db::builder()`?",
693 src_model.name().upper_camel_case(),
694 field_name,
695 )));
696 }
697 };
698
699 if pair.model != target {
700 return Err(crate::Error::invalid_schema(format!(
701 "field `{}::{}` specifies a `pair` on a model other than its target `{}`",
702 src_model.name().upper_camel_case(),
703 field_name,
704 target_model.name().upper_camel_case(),
705 )));
706 }
707
708 let paired = &target_model.as_root_unwrap().fields[pair.index];
709 match &paired.ty {
710 FieldTy::BelongsTo(rel) if rel.target == src => Ok(()),
711 _ => Err(crate::Error::invalid_schema(format!(
712 "field `{}::{}` specifies `pair = {}`, but `{}::{}` is not a `BelongsTo` \
713 targeting `{}`",
714 src_model.name().upper_camel_case(),
715 field_name,
716 paired.name.app_unwrap(),
717 target_model.name().upper_camel_case(),
718 paired.name.app_unwrap(),
719 src_model.name().upper_camel_case(),
720 ))),
721 }
722 }
723}
724
725fn eager_relation_target(field: &Field) -> Option<ModelId> {
726 if field.deferred {
727 return None;
728 }
729
730 field.relation_target_id()
731}