1#![deny(missing_docs)]
2
3use std::marker::PhantomData;
4use std::sync::Arc;
5
6use type_bridge_contract::id::is_canonical_thing_iid;
7use type_bridge_orm::_manager::DynamicRelationManager;
8use type_bridge_orm::session::backend::TxType;
9use type_bridge_orm::{DynamicAttributeMap, DynamicRelationRow, DynamicRolePlayerInput};
10
11use crate::__codegen::{
12 CompleteModel, EncodedCreate, HydrationCapability, IntoEncodedCreate, RelationModel,
13 SubtypeRootModel,
14};
15use crate::error::{Error, ModelValidationPhase};
16use crate::hooks::{CrudOperation, HookRunner, LifecycleHook, ModelKind};
17use crate::relation_codec::{
18 hydrate_relation, lower_relation_create, resolve_discovered_relation,
19 resolve_relation_authority,
20};
21use crate::schema::Schema;
22use crate::{Database, Result};
23
24#[cfg(test)]
25mod tests;
26
27fn invalid_iid() -> Error {
28 Error::model_validation(
29 ModelValidationPhase::Input,
30 "invalid_iid",
31 vec!["iid".into()],
32 "IID is not canonical",
33 None,
34 )
35}
36
37fn schema_not_bound() -> Error {
38 Error::model_validation(
39 ModelValidationPhase::Input,
40 "schema_not_bound",
41 vec![],
42 "database is not schema-bound",
43 None,
44 )
45}
46
47fn missing_post_write_row() -> Error {
48 Error::model_validation(
49 ModelValidationPhase::Hydration,
50 "missing_post_write_row",
51 vec!["iid".into()],
52 "written relation was not returned",
53 None,
54 )
55}
56
57fn ambiguous_provider_row() -> Error {
58 Error::model_validation(
59 ModelValidationPhase::Hydration,
60 "ambiguous_provider_row",
61 vec!["iid".into()],
62 "provider returned multiple coalesced rows for one exact IID",
63 None,
64 )
65}
66
67pub(crate) fn one_coalesced_row(
68 mut rows: Vec<DynamicRelationRow>,
69) -> Result<Option<DynamicRelationRow>> {
70 match rows.len() {
71 0 => Ok(None),
72 1 => Ok(Some(rows.remove(0))),
73 _ => Err(ambiguous_provider_row()),
74 }
75}
76
77pub(crate) async fn rehydrate_written_relation<M>(
80 manager: &DynamicRelationManager<'_>,
81 iid: &str,
82 id: &type_bridge_contract::id::TypeId,
83 installed: &type_bridge_orm::InstalledRuntimeProjection,
84) -> Result<M>
85where
86 M: crate::__codegen::CompleteModel,
87{
88 let rows = manager
89 .get_by_iid_exact(iid)
90 .await
91 .map_err(Error::from_orm)?;
92 let row = one_coalesced_row(rows)?.ok_or_else(missing_post_write_row)?;
93 let hydrated = hydrate_relation(row, id, installed)?;
94 M::materialize(&hydrated, &HydrationCapability::new()).map_err(|error| {
95 crate::entity_codec::map_validation_error(error, ModelValidationPhase::Hydration)
96 })
97}
98
99pub struct RelationManager<'db, S: Schema, M: RelationModel<Schema = S>> {
103 db: &'db Database<S>,
104 hooks: HookRunner,
105 marker: PhantomData<M>,
106}
107
108impl<'db, S: Schema, M: RelationModel<Schema = S>> Clone for RelationManager<'db, S, M> {
109 fn clone(&self) -> Self {
110 Self {
111 db: self.db,
112 hooks: self.hooks.clone(),
113 marker: PhantomData,
114 }
115 }
116}
117
118impl<S: Schema, M: RelationModel<Schema = S>> RelationManager<'_, S, M> {
119 pub(crate) fn new(db: &Database<S>) -> RelationManager<'_, S, M> {
120 RelationManager {
121 db,
122 hooks: HookRunner::default(),
123 marker: PhantomData,
124 }
125 }
126}
127
128impl<S, M> RelationManager<'_, S, M>
129where
130 S: Schema,
131 M: RelationModel<Schema = S> + CompleteModel,
132{
133 pub fn add_hook(&mut self, hook: Arc<dyn LifecycleHook>) -> &mut Self {
136 self.hooks.add(hook);
137 self
138 }
139
140 fn encode_hook_input(input: &M::Create) -> Result<EncodedCreate> {
141 input.clone().into_encoded_create().map_err(|error| {
142 crate::entity_codec::map_validation_error(error, ModelValidationPhase::Input)
143 })
144 }
145
146 pub async fn insert(&self, input: M::Create) -> Result<M> {
150 if !self.hooks.has_hooks() {
151 return self.write(input, false).await;
152 }
153 let encoded = Self::encode_hook_input(&input)?;
154 let state = self
155 .hooks
156 .run_pre(
157 M::TYPE_ID_JSON,
158 ModelKind::Relation,
159 CrudOperation::Insert,
160 None,
161 Some(&encoded),
162 )
163 .await?;
164 let output = self.write(input, false).await?;
165 self.hooks
166 .run_post(
167 M::TYPE_ID_JSON,
168 ModelKind::Relation,
169 CrudOperation::Insert,
170 Some(output.iid()),
171 Some(&encoded),
172 state,
173 )
174 .await;
175 Ok(output)
176 }
177 pub async fn put(&self, input: M::Create) -> Result<M> {
182 if !self.hooks.has_hooks() {
183 return self.write(input, true).await;
184 }
185 let encoded = Self::encode_hook_input(&input)?;
186 let state = self
187 .hooks
188 .run_pre(
189 M::TYPE_ID_JSON,
190 ModelKind::Relation,
191 CrudOperation::Put,
192 None,
193 Some(&encoded),
194 )
195 .await?;
196 let output = self.write(input, true).await?;
197 self.hooks
198 .run_post(
199 M::TYPE_ID_JSON,
200 ModelKind::Relation,
201 CrudOperation::Put,
202 Some(output.iid()),
203 Some(&encoded),
204 state,
205 )
206 .await;
207 Ok(output)
208 }
209 pub async fn insert_many(&self, inputs: Vec<M::Create>) -> Result<Vec<M>> {
212 if inputs.is_empty() {
213 return Ok(Vec::new());
214 }
215 if !self.hooks.has_hooks() {
216 return self.write_many(inputs, false).await;
217 }
218 let encoded = inputs
219 .iter()
220 .map(Self::encode_hook_input)
221 .collect::<Result<Vec<_>>>()?;
222 let mut states = Vec::with_capacity(inputs.len());
223 for input in &encoded {
224 states.push(
225 self.hooks
226 .run_pre(
227 M::TYPE_ID_JSON,
228 ModelKind::Relation,
229 CrudOperation::Insert,
230 None,
231 Some(input),
232 )
233 .await?,
234 );
235 }
236 let outputs = self.write_many(inputs, false).await?;
237 for ((input, output), state) in encoded.iter().zip(&outputs).zip(states) {
238 self.hooks
239 .run_post(
240 M::TYPE_ID_JSON,
241 ModelKind::Relation,
242 CrudOperation::Insert,
243 Some(output.iid()),
244 Some(input),
245 state,
246 )
247 .await;
248 }
249 Ok(outputs)
250 }
251 pub async fn put_many(&self, inputs: Vec<M::Create>) -> Result<Vec<M>> {
256 if inputs.is_empty() {
257 return Ok(Vec::new());
258 }
259 if !self.hooks.has_hooks() {
260 return self.write_many(inputs, true).await;
261 }
262 let encoded = inputs
263 .iter()
264 .map(Self::encode_hook_input)
265 .collect::<Result<Vec<_>>>()?;
266 let mut states = Vec::with_capacity(inputs.len());
267 for input in &encoded {
268 states.push(
269 self.hooks
270 .run_pre(
271 M::TYPE_ID_JSON,
272 ModelKind::Relation,
273 CrudOperation::Put,
274 None,
275 Some(input),
276 )
277 .await?,
278 );
279 }
280 let outputs = self.write_many(inputs, true).await?;
281 for ((input, output), state) in encoded.iter().zip(&outputs).zip(states) {
282 self.hooks
283 .run_post(
284 M::TYPE_ID_JSON,
285 ModelKind::Relation,
286 CrudOperation::Put,
287 Some(output.iid()),
288 Some(input),
289 state,
290 )
291 .await;
292 }
293 Ok(outputs)
294 }
295 pub async fn update(&self, iid: &str, input: M::Create) -> Result<M> {
299 if !is_canonical_thing_iid(iid) {
300 return Err(invalid_iid());
301 }
302 if !self.hooks.has_hooks() {
303 return self.update_write(iid, input).await;
304 }
305 let encoded = Self::encode_hook_input(&input)?;
306 let state = self
307 .hooks
308 .run_pre(
309 M::TYPE_ID_JSON,
310 ModelKind::Relation,
311 CrudOperation::Update,
312 Some(iid),
313 Some(&encoded),
314 )
315 .await?;
316 let output = self.update_write(iid, input).await?;
317 self.hooks
318 .run_post(
319 M::TYPE_ID_JSON,
320 ModelKind::Relation,
321 CrudOperation::Update,
322 Some(output.iid()),
323 Some(&encoded),
324 state,
325 )
326 .await;
327 Ok(output)
328 }
329
330 async fn update_write(&self, iid: &str, input: M::Create) -> Result<M> {
331 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
332 let (id, descriptor) = resolve_relation_authority(
333 M::TYPE_ID_JSON,
334 installed,
335 ModelValidationPhase::Input,
336 true,
337 )?;
338 let prepared = lower_relation_create(input, &id, installed)?;
339 let tx = self
340 .db
341 .inner_orm()
342 .transaction_context(TxType::Write)
343 .await
344 .map_err(Error::from_orm)?;
345 let manager = DynamicRelationManager::with_canonical_transaction(
346 tx.clone(),
347 Arc::new(descriptor.clone()),
348 );
349 if let Err(error) = manager
350 .update_exact(iid, &prepared.attributes, &prepared.role_players)
351 .await
352 {
353 let _ = tx.rollback().await;
354 return Err(Error::from_orm(error));
355 }
356 let rows = match manager.get_by_iid_exact(iid).await {
357 Ok(rows) => rows,
358 Err(error) => {
359 let _ = tx.rollback().await;
360 return Err(Error::from_orm(error));
361 }
362 };
363 let row = match one_coalesced_row(rows) {
364 Ok(Some(row)) => row,
365 Ok(None) => {
366 let _ = tx.rollback().await;
367 return Err(missing_post_write_row());
368 }
369 Err(error) => {
370 let _ = tx.rollback().await;
371 return Err(error);
372 }
373 };
374 let hydrated = match hydrate_relation(row, &id, installed) {
375 Ok(value) => value,
376 Err(error) => {
377 let _ = tx.rollback().await;
378 return Err(error);
379 }
380 };
381 let value = match M::materialize(&hydrated, &HydrationCapability::new()) {
382 Ok(value) => value,
383 Err(error) => {
384 let mapped = crate::entity_codec::map_validation_error(
385 error,
386 ModelValidationPhase::Hydration,
387 );
388 let _ = tx.rollback().await;
389 return Err(mapped);
390 }
391 };
392 tx.commit().await.map_err(Error::from_orm)?;
393 Ok(value)
394 }
395 pub async fn delete(&self, iid: &str) -> Result<()> {
398 if !is_canonical_thing_iid(iid) {
399 return Err(invalid_iid());
400 }
401 if !self.hooks.has_hooks() {
402 return self.delete_write(iid).await;
403 }
404 let state = self
405 .hooks
406 .run_pre(
407 M::TYPE_ID_JSON,
408 ModelKind::Relation,
409 CrudOperation::Delete,
410 Some(iid),
411 None,
412 )
413 .await?;
414 self.delete_write(iid).await?;
415 self.hooks
416 .run_post(
417 M::TYPE_ID_JSON,
418 ModelKind::Relation,
419 CrudOperation::Delete,
420 Some(iid),
421 None,
422 state,
423 )
424 .await;
425 Ok(())
426 }
427
428 async fn delete_write(&self, iid: &str) -> Result<()> {
429 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
430 let (_id, descriptor) = resolve_relation_authority(
431 M::TYPE_ID_JSON,
432 installed,
433 ModelValidationPhase::Input,
434 true,
435 )?;
436 let tx = self
437 .db
438 .inner_orm()
439 .transaction_context(TxType::Write)
440 .await
441 .map_err(Error::from_orm)?;
442 let manager = DynamicRelationManager::with_canonical_transaction(
443 tx.clone(),
444 Arc::new(descriptor.clone()),
445 );
446 if let Err(error) = manager.delete_by_iid_exact(iid).await {
447 let _ = tx.rollback().await;
448 return Err(Error::from_orm(error));
449 }
450 tx.commit().await.map_err(Error::from_orm)
451 }
452
453 pub async fn update_many(&self, inputs: Vec<(String, M::Create)>) -> Result<Vec<M>> {
457 if inputs.is_empty() {
458 return Ok(Vec::new());
459 }
460 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
461 let (id, descriptor) = resolve_relation_authority(
462 M::TYPE_ID_JSON,
463 installed,
464 ModelValidationPhase::Input,
465 true,
466 )?;
467 let mut prepared = Vec::with_capacity(inputs.len());
468 for (iid, input) in inputs {
469 if !is_canonical_thing_iid(&iid) {
470 return Err(invalid_iid());
471 }
472 let encoded = Self::encode_hook_input(&input)?;
473 let lowered = lower_relation_create(input, &id, installed)?;
474 prepared.push((iid, encoded, lowered));
475 }
476
477 let mut states = Vec::new();
478 if self.hooks.has_hooks() {
479 states.reserve(prepared.len());
480 for (iid, encoded, _) in &prepared {
481 states.push(
482 self.hooks
483 .run_pre(
484 M::TYPE_ID_JSON,
485 ModelKind::Relation,
486 CrudOperation::Update,
487 Some(iid),
488 Some(encoded),
489 )
490 .await?,
491 );
492 }
493 }
494
495 let tx = self
496 .db
497 .inner_orm()
498 .transaction_context(TxType::Write)
499 .await
500 .map_err(Error::from_orm)?;
501 let manager =
502 DynamicRelationManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
503 let mut outputs = Vec::with_capacity(prepared.len());
504 for (iid, _, lowered) in &prepared {
505 if let Err(error) = manager
506 .update_exact(iid, &lowered.attributes, &lowered.role_players)
507 .await
508 {
509 let _ = tx.rollback().await;
510 return Err(Error::from_orm(error));
511 }
512 match rehydrate_written_relation::<M>(&manager, iid, &id, installed).await {
513 Ok(output) => outputs.push(output),
514 Err(error) => {
515 let _ = tx.rollback().await;
516 return Err(error);
517 }
518 }
519 }
520 tx.commit().await.map_err(Error::from_orm)?;
521
522 if self.hooks.has_hooks() {
523 for (((_, encoded, _), output), state) in prepared.iter().zip(&outputs).zip(states) {
524 self.hooks
525 .run_post(
526 M::TYPE_ID_JSON,
527 ModelKind::Relation,
528 CrudOperation::Update,
529 Some(output.iid()),
530 Some(encoded),
531 state,
532 )
533 .await;
534 }
535 }
536 Ok(outputs)
537 }
538
539 pub async fn delete_many(&self, iids: &[String]) -> Result<()> {
543 if iids.is_empty() {
544 return Ok(());
545 }
546 if iids.iter().any(|iid| !is_canonical_thing_iid(iid)) {
547 return Err(invalid_iid());
548 }
549 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
550 let (_id, descriptor) = resolve_relation_authority(
551 M::TYPE_ID_JSON,
552 installed,
553 ModelValidationPhase::Input,
554 true,
555 )?;
556
557 let mut states = Vec::new();
558 if self.hooks.has_hooks() {
559 states.reserve(iids.len());
560 for iid in iids {
561 states.push(
562 self.hooks
563 .run_pre(
564 M::TYPE_ID_JSON,
565 ModelKind::Relation,
566 CrudOperation::Delete,
567 Some(iid),
568 None,
569 )
570 .await?,
571 );
572 }
573 }
574
575 let tx = self
576 .db
577 .inner_orm()
578 .transaction_context(TxType::Write)
579 .await
580 .map_err(Error::from_orm)?;
581 let manager =
582 DynamicRelationManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
583 for iid in iids {
584 if let Err(error) = manager.delete_by_iid_exact(iid).await {
585 let _ = tx.rollback().await;
586 return Err(Error::from_orm(error));
587 }
588 }
589 tx.commit().await.map_err(Error::from_orm)?;
590
591 if self.hooks.has_hooks() {
592 for (iid, state) in iids.iter().zip(states) {
593 self.hooks
594 .run_post(
595 M::TYPE_ID_JSON,
596 ModelKind::Relation,
597 CrudOperation::Delete,
598 Some(iid),
599 None,
600 state,
601 )
602 .await;
603 }
604 }
605 Ok(())
606 }
607
608 async fn write(&self, input: M::Create, put: bool) -> Result<M> {
609 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
610 let (id, descriptor) = resolve_relation_authority(
611 M::TYPE_ID_JSON,
612 installed,
613 ModelValidationPhase::Input,
614 true,
615 )?;
616 let prepared = lower_relation_create(input, &id, installed)?;
617 let tx = self
618 .db
619 .inner_orm()
620 .transaction_context(TxType::Write)
621 .await
622 .map_err(Error::from_orm)?;
623 let manager = DynamicRelationManager::with_canonical_transaction(
624 tx.clone(),
625 Arc::new(descriptor.clone()),
626 );
627 let iid = if put {
628 manager
629 .put_exact(&prepared.attributes, &prepared.role_players)
630 .await
631 } else {
632 manager
633 .insert(&prepared.attributes, &prepared.role_players)
634 .await
635 };
636 let iid = match iid {
637 Ok(iid) => iid,
638 Err(error) => {
639 let _ = tx.rollback().await;
640 return Err(Error::from_orm(error));
641 }
642 };
643 let value = match self
644 .hydrate_in_transaction(&manager, &iid, &id, installed)
645 .await
646 {
647 Ok(value) => value,
648 Err(error) => {
649 let _ = tx.rollback().await;
650 return Err(error);
651 }
652 };
653 tx.commit().await.map_err(Error::from_orm)?;
654 Ok(value)
655 }
656
657 async fn write_many(&self, inputs: Vec<M::Create>, put: bool) -> Result<Vec<M>> {
658 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
659 let (id, descriptor) = resolve_relation_authority(
660 M::TYPE_ID_JSON,
661 installed,
662 ModelValidationPhase::Input,
663 true,
664 )?;
665 let mut lowered: Vec<(DynamicAttributeMap, Vec<DynamicRolePlayerInput>)> =
666 Vec::with_capacity(inputs.len());
667 for input in inputs {
668 let prepared = lower_relation_create(input, &id, installed)?;
669 lowered.push((prepared.attributes, prepared.role_players));
670 }
671 let tx = self
672 .db
673 .inner_orm()
674 .transaction_context(TxType::Write)
675 .await
676 .map_err(Error::from_orm)?;
677 let manager =
678 DynamicRelationManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
679 let iids = match if put {
680 manager.put_many_exact(&lowered).await
681 } else {
682 manager.insert_many(&lowered).await
683 } {
684 Ok(iids) if iids.len() == lowered.len() => iids,
685 Ok(_) => {
686 let _ = tx.rollback().await;
687 return Err(Error::model_validation(
688 ModelValidationPhase::Hydration,
689 "iid_count_mismatch",
690 vec!["iid".into()],
691 "provider returned an unexpected IID count",
692 None,
693 ));
694 }
695 Err(error) => {
696 let _ = tx.rollback().await;
697 return Err(Error::from_orm(error));
698 }
699 };
700 let mut out = Vec::with_capacity(iids.len());
701 for iid in iids {
702 match self
703 .hydrate_in_transaction(&manager, &iid, &id, installed)
704 .await
705 {
706 Ok(value) => out.push(value),
707 Err(error) => {
708 let _ = tx.rollback().await;
709 return Err(error);
710 }
711 }
712 }
713 tx.commit().await.map_err(Error::from_orm)?;
714 Ok(out)
715 }
716
717 async fn hydrate_in_transaction(
718 &self,
719 manager: &DynamicRelationManager<'_>,
720 iid: &str,
721 id: &type_bridge_contract::id::TypeId,
722 installed: &type_bridge_orm::InstalledRuntimeProjection,
723 ) -> Result<M> {
724 rehydrate_written_relation(manager, iid, id, installed).await
725 }
726
727 pub async fn count(&self) -> Result<u64> {
729 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
730 let (_id, descriptor) = resolve_relation_authority(
731 M::TYPE_ID_JSON,
732 installed,
733 ModelValidationPhase::Input,
734 true,
735 )?;
736 DynamicRelationManager::new_canonical(self.db.inner_orm(), Arc::new(descriptor.clone()))
737 .count_exact()
738 .await
739 .map_err(Error::from_orm)
740 }
741
742 pub async fn get_by_iid(&self, iid: &str) -> Result<Option<M>> {
745 if !is_canonical_thing_iid(iid) {
746 return Err(invalid_iid());
747 }
748 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
749 let (id, descriptor) = resolve_relation_authority(
750 M::TYPE_ID_JSON,
751 installed,
752 ModelValidationPhase::Input,
753 true,
754 )?;
755 let rows = DynamicRelationManager::new_canonical(
756 self.db.inner_orm(),
757 Arc::new(descriptor.clone()),
758 )
759 .get_by_iid_exact(iid)
760 .await
761 .map_err(Error::from_orm)?;
762 match one_coalesced_row(rows)? {
763 None => Ok(None),
764 Some(row) => {
765 let hydrated = hydrate_relation(row, &id, installed)?;
766 let value =
767 M::materialize(&hydrated, &HydrationCapability::new()).map_err(|error| {
768 crate::entity_codec::map_validation_error(
769 error,
770 ModelValidationPhase::Hydration,
771 )
772 })?;
773 Ok(Some(value))
774 }
775 }
776 }
777
778 pub async fn all(&self) -> Result<Vec<M>> {
781 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
782 let (id, descriptor) = resolve_relation_authority(
783 M::TYPE_ID_JSON,
784 installed,
785 ModelValidationPhase::Input,
786 true,
787 )?;
788 let rows = DynamicRelationManager::new_canonical(
789 self.db.inner_orm(),
790 Arc::new(descriptor.clone()),
791 )
792 .all_exact()
793 .await
794 .map_err(Error::from_orm)?;
795 rows.into_iter()
796 .map(|row| {
797 let hydrated = hydrate_relation(row, &id, installed)?;
798 M::materialize(&hydrated, &HydrationCapability::new()).map_err(|error| {
799 crate::entity_codec::map_validation_error(
800 error,
801 ModelValidationPhase::Hydration,
802 )
803 })
804 })
805 .collect()
806 }
807}
808
809pub struct RelationSubtypeManager<
814 'db,
815 S: Schema,
816 M: SubtypeRootModel<Schema = S> + RelationModel<Schema = S>,
817> {
818 db: &'db Database<S>,
819 marker: PhantomData<M>,
820}
821
822impl<'db, S: Schema, M: SubtypeRootModel<Schema = S> + RelationModel<Schema = S>>
823 RelationSubtypeManager<'db, S, M>
824{
825 pub(crate) fn new(db: &'db Database<S>) -> Self {
826 Self {
827 db,
828 marker: PhantomData,
829 }
830 }
831}
832
833impl<'db, S, M> RelationManager<'db, S, M>
834where
835 S: Schema,
836 M: SubtypeRootModel<Schema = S> + RelationModel<Schema = S>,
837{
838 pub fn subtypes(&self) -> RelationSubtypeManager<'db, S, M> {
841 RelationSubtypeManager::new(self.db)
842 }
843}
844
845impl<S, M> RelationSubtypeManager<'_, S, M>
846where
847 S: Schema,
848 M: SubtypeRootModel<Schema = S> + RelationModel<Schema = S>,
849{
850 fn missing_concrete_row() -> Error {
851 Error::model_validation(
852 ModelValidationPhase::Hydration,
853 "missing_concrete_row",
854 vec!["iid".into()],
855 "discovered relation row is missing",
856 None,
857 )
858 }
859
860 async fn rehydrate_discovered(
861 tx: &type_bridge_orm::session::context::TransactionContext,
862 identity: &type_bridge_orm::DynamicRelationIdentity,
863 installed: &type_bridge_orm::InstalledRuntimeProjection,
864 ) -> Result<M::Subtypes> {
865 let (child_id, child_descriptor) =
866 resolve_discovered_relation(&identity.type_name, installed)?;
867 let child = DynamicRelationManager::with_canonical_transaction(
868 tx.clone(),
869 Arc::new(child_descriptor),
870 );
871 let rows = child
872 .get_by_iid_exact(&identity.iid)
873 .await
874 .map_err(Error::from_orm)?;
875 let row = one_coalesced_row(rows)?.ok_or_else(Self::missing_concrete_row)?;
876 let hydrated = hydrate_relation(row, &child_id, installed)?;
877 M::__tb_dispatch_subtype(&hydrated, &HydrationCapability::new()).map_err(|error| {
878 crate::entity_codec::map_validation_error(error, ModelValidationPhase::Hydration)
879 })
880 }
881
882 pub async fn get_by_iid(&self, iid: &str) -> Result<Option<M::Subtypes>> {
885 if !is_canonical_thing_iid(iid) {
886 return Err(invalid_iid());
887 }
888 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
889 let (_id, descriptor) = resolve_relation_authority(
890 M::TYPE_ID_JSON,
891 installed,
892 ModelValidationPhase::Input,
893 false,
894 )?;
895 let tx = self
896 .db
897 .inner_orm()
898 .transaction_context(TxType::Read)
899 .await
900 .map_err(Error::from_orm)?;
901 let manager =
902 DynamicRelationManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
903 let identity = match manager.discover_by_iid(iid).await {
904 Ok(value) => value,
905 Err(error) => {
906 let _ = tx.close().await;
907 return Err(Error::from_orm(error));
908 }
909 };
910 let out = match identity {
911 None => None,
912 Some(identity) => match Self::rehydrate_discovered(&tx, &identity, installed).await {
913 Ok(value) => Some(value),
914 Err(error) => {
915 let _ = tx.close().await;
916 return Err(error);
917 }
918 },
919 };
920 tx.close().await.map_err(Error::from_orm)?;
921 Ok(out)
922 }
923
924 pub async fn all(&self) -> Result<Vec<M::Subtypes>> {
928 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
929 let (_id, descriptor) = resolve_relation_authority(
930 M::TYPE_ID_JSON,
931 installed,
932 ModelValidationPhase::Input,
933 false,
934 )?;
935 let tx = self
936 .db
937 .inner_orm()
938 .transaction_context(TxType::Read)
939 .await
940 .map_err(Error::from_orm)?;
941 let manager =
942 DynamicRelationManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
943 let identities = match manager.discover_all().await {
944 Ok(value) => value,
945 Err(error) => {
946 let _ = tx.close().await;
947 return Err(Error::from_orm(error));
948 }
949 };
950 let mut out = Vec::with_capacity(identities.len());
951 for identity in identities {
952 match Self::rehydrate_discovered(&tx, &identity, installed).await {
953 Ok(value) => out.push(value),
954 Err(error) => {
955 let _ = tx.close().await;
956 return Err(error);
957 }
958 }
959 }
960 tx.close().await.map_err(Error::from_orm)?;
961 Ok(out)
962 }
963
964 pub async fn count(&self) -> Result<u64> {
966 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
967 let (_id, descriptor) = resolve_relation_authority(
968 M::TYPE_ID_JSON,
969 installed,
970 ModelValidationPhase::Input,
971 false,
972 )?;
973 DynamicRelationManager::new_canonical(self.db.inner_orm(), Arc::new(descriptor))
974 .count()
975 .await
976 .map_err(Error::from_orm)
977 }
978}