1use super::{EnumVariant, Field, FieldId, FieldPrimitive, FieldTy, Model, ModelId};
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 models(&self) -> impl Iterator<Item = &Model> {
83 self.models.values()
84 }
85
86 pub fn get_model(&self, id: impl Into<ModelId>) -> Option<&Model> {
88 self.models.get(&id.into())
89 }
90
91 pub fn model(&self, id: impl Into<ModelId>) -> &Model {
97 self.models.get(&id.into()).expect("invalid model ID")
98 }
99
100 pub fn fields(&self, id: impl Into<ModelId>) -> &[Field] {
111 self.model(id).fields()
112 }
113
114 pub fn project_fields<'s>(
131 &'s self,
132 model: ModelId,
133 projection: &[usize],
134 ) -> impl Iterator<Item = &'s Field> {
135 let mut current = Some(model);
136 let mut steps = projection.iter();
137
138 std::iter::from_fn(move || {
139 let &index = steps.next()?;
140 let field = self.get_model(current?)?.fields().get(index)?;
141 current = match field.expr_ty() {
142 stmt::Type::Model(nested) => Some(*nested),
143 _ => None,
144 };
145 Some(field)
146 })
147 }
148
149 pub fn resolve<'a>(
161 &'a self,
162 root: &'a Model,
163 projection: &stmt::Projection,
164 ) -> Option<Resolved<'a>> {
165 let [first, rest @ ..] = projection.as_slice() else {
166 return None;
167 };
168
169 let mut current_field = root.as_root_unwrap().fields.get(*first)?;
171
172 let mut steps = rest.iter();
175 while let Some(step) = steps.next() {
176 match ¤t_field.ty {
177 FieldTy::Primitive(FieldPrimitive {
184 ty: stmt::Type::Model(embed_id),
185 ..
186 }) => {
187 let consumed = rest.len() - steps.as_slice().len();
192 let doc_path = &rest[consumed - 1..];
193 return (self.project_fields(*embed_id, doc_path).count() == doc_path.len())
194 .then_some(Resolved::Field(current_field));
195 }
196 FieldTy::Primitive(..) => {
197 return None;
199 }
200 FieldTy::Embedded(embedded) => {
201 let target = self.model(embedded.target);
202 match target {
203 Model::EmbeddedStruct(s) => {
204 current_field = s.fields.get(*step)?;
205 }
206 Model::EmbeddedEnum(e) => {
207 let variant = e.variants.get(*step)?;
208
209 if let Some(field_step) = steps.next() {
211 current_field = e.fields.get(*field_step)?;
213 } else {
214 return Some(Resolved::Variant(variant));
216 }
217 }
218 _ => return None,
219 }
220 }
221 FieldTy::BelongsTo(belongs_to) => {
222 current_field = belongs_to.target(self).as_root_unwrap().fields.get(*step)?;
223 }
224 FieldTy::Has(has) => {
225 current_field = has.target(self).as_root_unwrap().fields.get(*step)?;
226 }
227 FieldTy::Via(via) => {
228 current_field = via.target(self).as_root_unwrap().fields.get(*step)?;
229 }
230 };
231 }
232
233 Some(Resolved::Field(current_field))
234 }
235
236 pub fn resolve_field<'a>(
241 &'a self,
242 root: &'a Model,
243 projection: &stmt::Projection,
244 ) -> Option<&'a Field> {
245 match self.resolve(root, projection) {
246 Some(Resolved::Field(field)) => Some(field),
247 _ => None,
248 }
249 }
250
251 pub fn resolve_field_path<'a>(&'a self, path: &stmt::Path) -> Option<&'a Field> {
254 let model = self.model(path.root.as_model_unwrap());
255 self.resolve_field(model, &path.projection)
256 }
257}
258
259impl Builder {
260 pub(crate) fn from_macro(models: impl IntoIterator<Item = Model>) -> Result<Schema> {
261 let mut builder = Self { ..Self::default() };
262
263 for model in models {
264 builder.models.insert(model.id(), model);
265 }
266
267 builder.process_models()?;
268 builder.into_schema()
269 }
270
271 fn into_schema(self) -> Result<Schema> {
272 Ok(Schema {
273 models: self.models,
274 })
275 }
276
277 fn process_models(&mut self) -> Result<()> {
278 self.link_relations()?;
281 self.resolve_via_targets()?;
282 self.verify_no_eager_load_cycles()?;
283
284 Ok(())
285 }
286
287 fn resolve_via_targets(&mut self) -> crate::Result<()> {
296 let mut updates = Vec::new();
298
299 for curr in 0..self.models.len() {
300 if self.models[curr].is_embedded() {
301 continue;
302 }
303 let src = self.models[curr].id();
304 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
305 let field = &self.models[curr].as_root_unwrap().fields[index];
306 let FieldTy::Via(via) = &field.ty else {
307 continue;
308 };
309 let Some(terminal) = via.terminal else {
310 continue;
311 };
312
313 let projection = via.path.projection.as_slice();
315 let relation_steps = &projection[..projection.len() - 1];
316 let field_name = field.name.app_unwrap().to_string();
317 let target = self.walk_via_relation_chain(src, relation_steps, &field_name)?;
318
319 let terminal_field = &self.models[&target].as_root_unwrap().fields[terminal];
321 if !matches!(terminal_field.ty, FieldTy::Primitive(_)) {
322 return Err(crate::Error::invalid_schema(format!(
323 "the `via` terminal `{}::{}` is not a scalar field",
324 self.models[&target].name().upper_camel_case(),
325 terminal_field.name.app_unwrap(),
326 )));
327 }
328
329 updates.push((curr, index, target));
330 }
331 }
332
333 for (curr, index, target) in updates {
334 if let FieldTy::Via(via) = &mut self.models[curr].as_root_mut_unwrap().fields[index].ty
335 {
336 via.target = target;
337 }
338 }
339
340 Ok(())
341 }
342
343 fn walk_via_relation_chain(
346 &self,
347 declaring: ModelId,
348 steps: &[usize],
349 field_name: &str,
350 ) -> crate::Result<ModelId> {
351 let mut current = declaring;
352 let mut queue: Vec<usize> = steps.iter().rev().copied().collect();
353
354 while let Some(idx) = queue.pop() {
355 let field = &self.models[¤t].as_root_unwrap().fields[idx];
356 match &field.ty {
357 FieldTy::Has(has) => current = has.target,
358 FieldTy::BelongsTo(belongs_to) => current = belongs_to.target,
359 FieldTy::Via(inner) => {
362 let inner_projection = inner.path.projection.as_slice();
363 let inner_steps = match inner.terminal {
364 Some(_) => &inner_projection[..inner_projection.len() - 1],
365 None => inner_projection,
366 };
367 for step in inner_steps.iter().rev() {
368 queue.push(*step);
369 }
370 }
371 _ => {
372 return Err(crate::Error::invalid_schema(format!(
373 "the `via` path for `{}::{}` traverses `{}`, which is not a relation",
374 self.models[&declaring].name().upper_camel_case(),
375 field_name,
376 field.name.app_unwrap(),
377 )));
378 }
379 }
380 }
381
382 Ok(current)
383 }
384
385 fn verify_no_eager_load_cycles(&self) -> crate::Result<()> {
386 let mut visited = HashSet::new();
387 let mut model_stack = Vec::new();
388 let mut field_stack = Vec::new();
389
390 for model in self.models.values() {
391 if model.is_embedded() {
392 continue;
393 }
394 self.visit_eager_load_graph(
395 model.id(),
396 &mut visited,
397 &mut model_stack,
398 &mut field_stack,
399 )?;
400 }
401
402 Ok(())
403 }
404
405 fn visit_eager_load_graph(
406 &self,
407 model_id: ModelId,
408 visited: &mut HashSet<ModelId>,
409 model_stack: &mut Vec<ModelId>,
410 field_stack: &mut Vec<FieldId>,
411 ) -> crate::Result<()> {
412 if model_stack.contains(&model_id) {
413 return Ok(());
414 }
415
416 if !visited.insert(model_id) {
417 return Ok(());
418 }
419
420 model_stack.push(model_id);
421
422 let model = self.models[&model_id].as_root_unwrap();
423 for field in &model.fields {
424 let Some(target) = eager_relation_target(field) else {
425 continue;
426 };
427
428 if let Some(pos) = model_stack.iter().position(|id| *id == target) {
429 let mut cycle = field_stack[pos..].to_vec();
430 cycle.push(field.id);
431 return Err(crate::Error::invalid_schema(format!(
432 "eager relation cycle detected: {}",
433 self.format_eager_load_cycle(&cycle, target)
434 )));
435 }
436
437 field_stack.push(field.id);
438 self.visit_eager_load_graph(target, visited, model_stack, field_stack)?;
439 field_stack.pop();
440 }
441
442 model_stack.pop();
443 Ok(())
444 }
445
446 fn format_eager_load_cycle(&self, fields: &[FieldId], target: ModelId) -> String {
447 let mut parts = Vec::new();
448 for field_id in fields {
449 let model = &self.models[&field_id.model];
450 let field = &model.as_root_unwrap().fields[field_id.index];
451 parts.push(format!(
452 "{}::{}",
453 model.name().upper_camel_case(),
454 field.name.app_unwrap()
455 ));
456 }
457 parts.push(self.models[&target].name().upper_camel_case());
458 parts.join(" -> ")
459 }
460
461 fn link_relations(&mut self) -> crate::Result<()> {
463 for curr in 0..self.models.len() {
471 if self.models[curr].is_embedded() {
472 continue;
473 }
474 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
475 let model = &self.models[curr];
476 let src = model.id();
477 let field = &model.as_root_unwrap().fields[index];
478
479 if let FieldTy::Has(has) = &field.ty
480 && has.is_many()
481 {
482 let target = has.target;
483 let field_name = field.name.app_unwrap().to_string();
484 let pair = if has.pair_id.is_placeholder() {
485 self.find_has_many_pair(src, target, &field_name)?
486 } else {
487 self.validate_pair(src, target, &field_name, has.pair_id)?;
488 has.pair_id
489 };
490 self.models[curr].as_root_mut_unwrap().fields[index]
491 .ty
492 .as_has_mut_unwrap()
493 .pair_id = pair;
494 }
495 }
496 }
497
498 for curr in 0..self.models.len() {
500 if self.models[curr].is_embedded() {
501 continue;
502 }
503 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
504 let model = &self.models[curr];
505 let src = model.id();
506 let field = &model.as_root_unwrap().fields[index];
507
508 match &field.ty {
509 FieldTy::Has(has) if has.is_one() => {
510 let target = has.target;
511 let field_name = field.name.app_unwrap().to_string();
512 let pair = if has.pair_id.is_placeholder() {
513 match self.find_belongs_to_pair(src, target, &field_name)? {
514 Some(pair) => pair,
515 None => {
516 return Err(crate::Error::invalid_schema(format!(
517 "field `{}::{}` has no matching `BelongsTo` relation on the target model",
518 self.models[curr].name().upper_camel_case(),
519 field_name,
520 )));
521 }
522 }
523 } else {
524 self.validate_pair(src, target, &field_name, has.pair_id)?;
525 has.pair_id
526 };
527
528 self.models[curr].as_root_mut_unwrap().fields[index]
529 .ty
530 .as_has_mut_unwrap()
531 .pair_id = pair;
532 }
533 FieldTy::BelongsTo(belongs_to) => {
534 assert!(!belongs_to.foreign_key.is_placeholder());
535 continue;
536 }
537 _ => {}
538 }
539 }
540 }
541
542 for curr in 0..self.models.len() {
544 if self.models[curr].is_embedded() {
545 continue;
546 }
547 for index in 0..self.models[curr].as_root_unwrap().fields.len() {
548 let model = &self.models[curr];
549 let field_id = model.as_root_unwrap().fields[index].id;
550
551 let pair = match &self.models[curr].as_root_unwrap().fields[index].ty {
552 FieldTy::BelongsTo(belongs_to) => {
553 let mut pair = None;
554 let target = match self.models.get_index_of(&belongs_to.target) {
555 Some(target) => target,
556 None => {
557 let model = &self.models[curr];
558 return Err(crate::Error::invalid_schema(format!(
559 "field `{}::{}` references a model that was not registered \
560 with the schema; did you forget to register it with `Db::builder()`?",
561 model.name().upper_camel_case(),
562 model.as_root_unwrap().fields[index].name(),
563 )));
564 }
565 };
566
567 for target_index in 0..self.models[target].as_root_unwrap().fields.len() {
568 pair = match &self.models[target].as_root_unwrap().fields[target_index]
569 .ty
570 {
571 FieldTy::Has(has) if has.pair_id == field_id => {
572 assert!(pair.is_none());
573 Some(
574 self.models[target].as_root_unwrap().fields[target_index]
575 .id,
576 )
577 }
578 _ => continue,
579 }
580 }
581
582 if pair.is_none() {
583 continue;
584 }
585
586 pair
587 }
588 _ => continue,
589 };
590
591 self.models[curr].as_root_mut_unwrap().fields[index]
592 .ty
593 .as_belongs_to_mut_unwrap()
594 .pair = pair;
595 }
596 }
597
598 Ok(())
599 }
600
601 fn find_belongs_to_pair(
602 &self,
603 src: ModelId,
604 target: ModelId,
605 field_name: &str,
606 ) -> crate::Result<Option<FieldId>> {
607 let src_model = &self.models[&src];
608
609 let target = match self.models.get(&target) {
610 Some(target) => target,
611 None => {
612 return Err(crate::Error::invalid_schema(format!(
613 "field `{}::{}` references a model that was not registered with the schema; \
614 did you forget to register it with `Db::builder()`?",
615 src_model.name().upper_camel_case(),
616 field_name,
617 )));
618 }
619 };
620
621 let belongs_to: Vec<_> = target
623 .as_root_unwrap()
624 .fields
625 .iter()
626 .filter(|field| match &field.ty {
627 FieldTy::BelongsTo(rel) => rel.target == src,
628 _ => false,
629 })
630 .collect();
631
632 match &belongs_to[..] {
633 [field] => Ok(Some(field.id)),
634 [] => Ok(None),
635 _ => Err(crate::Error::invalid_schema(format!(
636 "model `{}` has more than one `BelongsTo` relation targeting `{}`; \
637 disambiguate by adding `pair = <field>` on the paired `has_many`/`has_one` \
638 field",
639 target.name().upper_camel_case(),
640 src_model.name().upper_camel_case(),
641 ))),
642 }
643 }
644
645 fn find_has_many_pair(
646 &mut self,
647 src: ModelId,
648 target: ModelId,
649 field_name: &str,
650 ) -> crate::Result<FieldId> {
651 if let Some(field_id) = self.find_belongs_to_pair(src, target, field_name)? {
652 return Ok(field_id);
653 }
654
655 Err(crate::Error::invalid_schema(format!(
656 "field `{}::{}` has no matching `BelongsTo` relation on the target model",
657 self.models[&src].name().upper_camel_case(),
658 field_name,
659 )))
660 }
661
662 fn validate_pair(
666 &self,
667 src: ModelId,
668 target: ModelId,
669 field_name: &str,
670 pair: FieldId,
671 ) -> crate::Result<()> {
672 let src_model = &self.models[&src];
673
674 let target_model = match self.models.get(&target) {
675 Some(target) => target,
676 None => {
677 return Err(crate::Error::invalid_schema(format!(
678 "field `{}::{}` references a model that was not registered with the schema; \
679 did you forget to register it with `Db::builder()`?",
680 src_model.name().upper_camel_case(),
681 field_name,
682 )));
683 }
684 };
685
686 if pair.model != target {
687 return Err(crate::Error::invalid_schema(format!(
688 "field `{}::{}` specifies a `pair` on a model other than its target `{}`",
689 src_model.name().upper_camel_case(),
690 field_name,
691 target_model.name().upper_camel_case(),
692 )));
693 }
694
695 let paired = &target_model.as_root_unwrap().fields[pair.index];
696 match &paired.ty {
697 FieldTy::BelongsTo(rel) if rel.target == src => Ok(()),
698 _ => Err(crate::Error::invalid_schema(format!(
699 "field `{}::{}` specifies `pair = {}`, but `{}::{}` is not a `BelongsTo` \
700 targeting `{}`",
701 src_model.name().upper_camel_case(),
702 field_name,
703 paired.name.app_unwrap(),
704 target_model.name().upper_camel_case(),
705 paired.name.app_unwrap(),
706 src_model.name().upper_camel_case(),
707 ))),
708 }
709 }
710}
711
712fn eager_relation_target(field: &Field) -> Option<ModelId> {
713 if field.deferred {
714 return None;
715 }
716
717 field.relation_target_id()
718}