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::{CompleteModel, HydrationCapability, RelationModel, SubtypeRootModel};
12use crate::error::{Error, ModelValidationPhase};
13use crate::relation_codec::{
14 hydrate_relation, lower_relation_create, resolve_discovered_relation,
15 resolve_relation_authority,
16};
17use crate::schema::Schema;
18use crate::{Database, Result};
19
20#[cfg(test)]
21mod tests;
22
23fn invalid_iid() -> Error {
24 Error::model_validation(
25 ModelValidationPhase::Input,
26 "invalid_iid",
27 vec!["iid".into()],
28 "IID is not canonical",
29 None,
30 )
31}
32
33fn schema_not_bound() -> Error {
34 Error::model_validation(
35 ModelValidationPhase::Input,
36 "schema_not_bound",
37 vec![],
38 "database is not schema-bound",
39 None,
40 )
41}
42
43fn missing_post_write_row() -> Error {
44 Error::model_validation(
45 ModelValidationPhase::Hydration,
46 "missing_post_write_row",
47 vec!["iid".into()],
48 "written relation was not returned",
49 None,
50 )
51}
52
53fn ambiguous_provider_row() -> Error {
54 Error::model_validation(
55 ModelValidationPhase::Hydration,
56 "ambiguous_provider_row",
57 vec!["iid".into()],
58 "provider returned multiple coalesced rows for one exact IID",
59 None,
60 )
61}
62
63pub(crate) fn one_coalesced_row(
64 mut rows: Vec<DynamicRelationRow>,
65) -> Result<Option<DynamicRelationRow>> {
66 match rows.len() {
67 0 => Ok(None),
68 1 => Ok(Some(rows.remove(0))),
69 _ => Err(ambiguous_provider_row()),
70 }
71}
72
73pub(crate) async fn rehydrate_written_relation<M>(
76 manager: &DynamicRelationManager<'_>,
77 iid: &str,
78 id: &type_bridge_contract::id::TypeId,
79 installed: &type_bridge_orm::InstalledRuntimeProjection,
80) -> Result<M>
81where
82 M: crate::__codegen::CompleteModel,
83{
84 let rows = manager
85 .get_by_iid_exact(iid)
86 .await
87 .map_err(Error::from_orm)?;
88 let row = one_coalesced_row(rows)?.ok_or_else(missing_post_write_row)?;
89 let hydrated = hydrate_relation(row, id, installed)?;
90 M::materialize(&hydrated, &HydrationCapability::new()).map_err(|error| {
91 crate::entity_codec::map_validation_error(error, ModelValidationPhase::Hydration)
92 })
93}
94
95pub struct RelationManager<'db, S: Schema, M: RelationModel<Schema = S>> {
99 db: &'db Database<S>,
100 marker: PhantomData<M>,
101}
102
103impl<'db, S: Schema, M: RelationModel<Schema = S>> Copy for RelationManager<'db, S, M> {}
104impl<'db, S: Schema, M: RelationModel<Schema = S>> Clone for RelationManager<'db, S, M> {
105 fn clone(&self) -> Self {
106 *self
107 }
108}
109
110impl<S: Schema, M: RelationModel<Schema = S>> RelationManager<'_, S, M> {
111 pub(crate) fn new(db: &Database<S>) -> RelationManager<'_, S, M> {
112 RelationManager {
113 db,
114 marker: PhantomData,
115 }
116 }
117}
118
119impl<S, M> RelationManager<'_, S, M>
120where
121 S: Schema,
122 M: RelationModel<Schema = S> + CompleteModel,
123{
124 pub async fn insert(&self, input: M::Create) -> Result<M> {
128 self.write(input, false).await
129 }
130 pub async fn put(&self, input: M::Create) -> Result<M> {
135 self.write(input, true).await
136 }
137 pub async fn insert_many(&self, inputs: Vec<M::Create>) -> Result<Vec<M>> {
140 if inputs.is_empty() {
141 return Ok(Vec::new());
142 }
143 self.write_many(inputs, false).await
144 }
145 pub async fn put_many(&self, inputs: Vec<M::Create>) -> Result<Vec<M>> {
150 if inputs.is_empty() {
151 return Ok(Vec::new());
152 }
153 self.write_many(inputs, true).await
154 }
155 pub async fn update(&self, iid: &str, input: M::Create) -> Result<M> {
159 if !is_canonical_thing_iid(iid) {
160 return Err(invalid_iid());
161 }
162 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
163 let (id, descriptor) = resolve_relation_authority(
164 M::TYPE_ID_JSON,
165 installed,
166 ModelValidationPhase::Input,
167 true,
168 )?;
169 let prepared = lower_relation_create(input, &id, installed)?;
170 let tx = self
171 .db
172 .inner_orm()
173 .transaction_context(TxType::Write)
174 .await
175 .map_err(Error::from_orm)?;
176 let manager = DynamicRelationManager::with_canonical_transaction(
177 tx.clone(),
178 Arc::new(descriptor.clone()),
179 );
180 if let Err(error) = manager
181 .update_exact(iid, &prepared.attributes, &prepared.role_players)
182 .await
183 {
184 let _ = tx.rollback().await;
185 return Err(Error::from_orm(error));
186 }
187 let rows = match manager.get_by_iid_exact(iid).await {
188 Ok(rows) => rows,
189 Err(error) => {
190 let _ = tx.rollback().await;
191 return Err(Error::from_orm(error));
192 }
193 };
194 let row = match one_coalesced_row(rows) {
195 Ok(Some(row)) => row,
196 Ok(None) => {
197 let _ = tx.rollback().await;
198 return Err(missing_post_write_row());
199 }
200 Err(error) => {
201 let _ = tx.rollback().await;
202 return Err(error);
203 }
204 };
205 let hydrated = match hydrate_relation(row, &id, installed) {
206 Ok(value) => value,
207 Err(error) => {
208 let _ = tx.rollback().await;
209 return Err(error);
210 }
211 };
212 let value = match M::materialize(&hydrated, &HydrationCapability::new()) {
213 Ok(value) => value,
214 Err(error) => {
215 let mapped = crate::entity_codec::map_validation_error(
216 error,
217 ModelValidationPhase::Hydration,
218 );
219 let _ = tx.rollback().await;
220 return Err(mapped);
221 }
222 };
223 tx.commit().await.map_err(Error::from_orm)?;
224 Ok(value)
225 }
226 pub async fn delete(&self, iid: &str) -> Result<()> {
229 if !is_canonical_thing_iid(iid) {
230 return Err(invalid_iid());
231 }
232 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
233 let (_id, descriptor) = resolve_relation_authority(
234 M::TYPE_ID_JSON,
235 installed,
236 ModelValidationPhase::Input,
237 true,
238 )?;
239 let tx = self
240 .db
241 .inner_orm()
242 .transaction_context(TxType::Write)
243 .await
244 .map_err(Error::from_orm)?;
245 let manager = DynamicRelationManager::with_canonical_transaction(
246 tx.clone(),
247 Arc::new(descriptor.clone()),
248 );
249 if let Err(error) = manager.delete_by_iid_exact(iid).await {
250 let _ = tx.rollback().await;
251 return Err(Error::from_orm(error));
252 }
253 tx.commit().await.map_err(Error::from_orm)
254 }
255 async fn write(&self, input: M::Create, put: bool) -> Result<M> {
256 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
257 let (id, descriptor) = resolve_relation_authority(
258 M::TYPE_ID_JSON,
259 installed,
260 ModelValidationPhase::Input,
261 true,
262 )?;
263 let prepared = lower_relation_create(input, &id, installed)?;
264 let tx = self
265 .db
266 .inner_orm()
267 .transaction_context(TxType::Write)
268 .await
269 .map_err(Error::from_orm)?;
270 let manager = DynamicRelationManager::with_canonical_transaction(
271 tx.clone(),
272 Arc::new(descriptor.clone()),
273 );
274 let iid = if put {
275 manager
276 .put_exact(&prepared.attributes, &prepared.role_players)
277 .await
278 } else {
279 manager
280 .insert(&prepared.attributes, &prepared.role_players)
281 .await
282 };
283 let iid = match iid {
284 Ok(iid) => iid,
285 Err(error) => {
286 let _ = tx.rollback().await;
287 return Err(Error::from_orm(error));
288 }
289 };
290 let value = match self
291 .hydrate_in_transaction(&manager, &iid, &id, installed)
292 .await
293 {
294 Ok(value) => value,
295 Err(error) => {
296 let _ = tx.rollback().await;
297 return Err(error);
298 }
299 };
300 tx.commit().await.map_err(Error::from_orm)?;
301 Ok(value)
302 }
303
304 async fn write_many(&self, inputs: Vec<M::Create>, put: bool) -> Result<Vec<M>> {
305 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
306 let (id, descriptor) = resolve_relation_authority(
307 M::TYPE_ID_JSON,
308 installed,
309 ModelValidationPhase::Input,
310 true,
311 )?;
312 let mut lowered: Vec<(DynamicAttributeMap, Vec<DynamicRolePlayerInput>)> =
313 Vec::with_capacity(inputs.len());
314 for input in inputs {
315 let prepared = lower_relation_create(input, &id, installed)?;
316 lowered.push((prepared.attributes, prepared.role_players));
317 }
318 let tx = self
319 .db
320 .inner_orm()
321 .transaction_context(TxType::Write)
322 .await
323 .map_err(Error::from_orm)?;
324 let manager =
325 DynamicRelationManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
326 let iids = match if put {
327 manager.put_many_exact(&lowered).await
328 } else {
329 manager.insert_many(&lowered).await
330 } {
331 Ok(iids) if iids.len() == lowered.len() => iids,
332 Ok(_) => {
333 let _ = tx.rollback().await;
334 return Err(Error::model_validation(
335 ModelValidationPhase::Hydration,
336 "iid_count_mismatch",
337 vec!["iid".into()],
338 "provider returned an unexpected IID count",
339 None,
340 ));
341 }
342 Err(error) => {
343 let _ = tx.rollback().await;
344 return Err(Error::from_orm(error));
345 }
346 };
347 let mut out = Vec::with_capacity(iids.len());
348 for iid in iids {
349 match self
350 .hydrate_in_transaction(&manager, &iid, &id, installed)
351 .await
352 {
353 Ok(value) => out.push(value),
354 Err(error) => {
355 let _ = tx.rollback().await;
356 return Err(error);
357 }
358 }
359 }
360 tx.commit().await.map_err(Error::from_orm)?;
361 Ok(out)
362 }
363
364 async fn hydrate_in_transaction(
365 &self,
366 manager: &DynamicRelationManager<'_>,
367 iid: &str,
368 id: &type_bridge_contract::id::TypeId,
369 installed: &type_bridge_orm::InstalledRuntimeProjection,
370 ) -> Result<M> {
371 rehydrate_written_relation(manager, iid, id, installed).await
372 }
373
374 pub async fn count(&self) -> Result<u64> {
376 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
377 let (_id, descriptor) = resolve_relation_authority(
378 M::TYPE_ID_JSON,
379 installed,
380 ModelValidationPhase::Input,
381 true,
382 )?;
383 DynamicRelationManager::new_canonical(self.db.inner_orm(), Arc::new(descriptor.clone()))
384 .count_exact()
385 .await
386 .map_err(Error::from_orm)
387 }
388
389 pub async fn get_by_iid(&self, iid: &str) -> Result<Option<M>> {
392 if !is_canonical_thing_iid(iid) {
393 return Err(invalid_iid());
394 }
395 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
396 let (id, descriptor) = resolve_relation_authority(
397 M::TYPE_ID_JSON,
398 installed,
399 ModelValidationPhase::Input,
400 true,
401 )?;
402 let rows = DynamicRelationManager::new_canonical(
403 self.db.inner_orm(),
404 Arc::new(descriptor.clone()),
405 )
406 .get_by_iid_exact(iid)
407 .await
408 .map_err(Error::from_orm)?;
409 match one_coalesced_row(rows)? {
410 None => Ok(None),
411 Some(row) => {
412 let hydrated = hydrate_relation(row, &id, installed)?;
413 let value =
414 M::materialize(&hydrated, &HydrationCapability::new()).map_err(|error| {
415 crate::entity_codec::map_validation_error(
416 error,
417 ModelValidationPhase::Hydration,
418 )
419 })?;
420 Ok(Some(value))
421 }
422 }
423 }
424
425 pub async fn all(&self) -> Result<Vec<M>> {
428 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
429 let (id, descriptor) = resolve_relation_authority(
430 M::TYPE_ID_JSON,
431 installed,
432 ModelValidationPhase::Input,
433 true,
434 )?;
435 let rows = DynamicRelationManager::new_canonical(
436 self.db.inner_orm(),
437 Arc::new(descriptor.clone()),
438 )
439 .all_exact()
440 .await
441 .map_err(Error::from_orm)?;
442 rows.into_iter()
443 .map(|row| {
444 let hydrated = hydrate_relation(row, &id, installed)?;
445 M::materialize(&hydrated, &HydrationCapability::new()).map_err(|error| {
446 crate::entity_codec::map_validation_error(
447 error,
448 ModelValidationPhase::Hydration,
449 )
450 })
451 })
452 .collect()
453 }
454}
455
456pub struct RelationSubtypeManager<
461 'db,
462 S: Schema,
463 M: SubtypeRootModel<Schema = S> + RelationModel<Schema = S>,
464> {
465 db: &'db Database<S>,
466 marker: PhantomData<M>,
467}
468
469impl<'db, S: Schema, M: SubtypeRootModel<Schema = S> + RelationModel<Schema = S>>
470 RelationSubtypeManager<'db, S, M>
471{
472 pub(crate) fn new(db: &'db Database<S>) -> Self {
473 Self {
474 db,
475 marker: PhantomData,
476 }
477 }
478}
479
480impl<'db, S, M> RelationManager<'db, S, M>
481where
482 S: Schema,
483 M: SubtypeRootModel<Schema = S> + RelationModel<Schema = S>,
484{
485 pub fn subtypes(&self) -> RelationSubtypeManager<'db, S, M> {
488 RelationSubtypeManager::new(self.db)
489 }
490}
491
492impl<S, M> RelationSubtypeManager<'_, S, M>
493where
494 S: Schema,
495 M: SubtypeRootModel<Schema = S> + RelationModel<Schema = S>,
496{
497 fn missing_concrete_row() -> Error {
498 Error::model_validation(
499 ModelValidationPhase::Hydration,
500 "missing_concrete_row",
501 vec!["iid".into()],
502 "discovered relation row is missing",
503 None,
504 )
505 }
506
507 async fn rehydrate_discovered(
508 tx: &type_bridge_orm::session::context::TransactionContext,
509 identity: &type_bridge_orm::DynamicRelationIdentity,
510 installed: &type_bridge_orm::InstalledRuntimeProjection,
511 ) -> Result<M::Subtypes> {
512 let (child_id, child_descriptor) =
513 resolve_discovered_relation(&identity.type_name, installed)?;
514 let child = DynamicRelationManager::with_canonical_transaction(
515 tx.clone(),
516 Arc::new(child_descriptor),
517 );
518 let rows = child
519 .get_by_iid_exact(&identity.iid)
520 .await
521 .map_err(Error::from_orm)?;
522 let row = one_coalesced_row(rows)?.ok_or_else(Self::missing_concrete_row)?;
523 let hydrated = hydrate_relation(row, &child_id, installed)?;
524 M::__tb_dispatch_subtype(&hydrated, &HydrationCapability::new()).map_err(|error| {
525 crate::entity_codec::map_validation_error(error, ModelValidationPhase::Hydration)
526 })
527 }
528
529 pub async fn get_by_iid(&self, iid: &str) -> Result<Option<M::Subtypes>> {
532 if !is_canonical_thing_iid(iid) {
533 return Err(invalid_iid());
534 }
535 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
536 let (_id, descriptor) = resolve_relation_authority(
537 M::TYPE_ID_JSON,
538 installed,
539 ModelValidationPhase::Input,
540 false,
541 )?;
542 let tx = self
543 .db
544 .inner_orm()
545 .transaction_context(TxType::Read)
546 .await
547 .map_err(Error::from_orm)?;
548 let manager =
549 DynamicRelationManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
550 let identity = match manager.discover_by_iid(iid).await {
551 Ok(value) => value,
552 Err(error) => {
553 let _ = tx.close().await;
554 return Err(Error::from_orm(error));
555 }
556 };
557 let out = match identity {
558 None => None,
559 Some(identity) => match Self::rehydrate_discovered(&tx, &identity, installed).await {
560 Ok(value) => Some(value),
561 Err(error) => {
562 let _ = tx.close().await;
563 return Err(error);
564 }
565 },
566 };
567 tx.close().await.map_err(Error::from_orm)?;
568 Ok(out)
569 }
570
571 pub async fn all(&self) -> Result<Vec<M::Subtypes>> {
575 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
576 let (_id, descriptor) = resolve_relation_authority(
577 M::TYPE_ID_JSON,
578 installed,
579 ModelValidationPhase::Input,
580 false,
581 )?;
582 let tx = self
583 .db
584 .inner_orm()
585 .transaction_context(TxType::Read)
586 .await
587 .map_err(Error::from_orm)?;
588 let manager =
589 DynamicRelationManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
590 let identities = match manager.discover_all().await {
591 Ok(value) => value,
592 Err(error) => {
593 let _ = tx.close().await;
594 return Err(Error::from_orm(error));
595 }
596 };
597 let mut out = Vec::with_capacity(identities.len());
598 for identity in identities {
599 match Self::rehydrate_discovered(&tx, &identity, installed).await {
600 Ok(value) => out.push(value),
601 Err(error) => {
602 let _ = tx.close().await;
603 return Err(error);
604 }
605 }
606 }
607 tx.close().await.map_err(Error::from_orm)?;
608 Ok(out)
609 }
610
611 pub async fn count(&self) -> Result<u64> {
613 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
614 let (_id, descriptor) = resolve_relation_authority(
615 M::TYPE_ID_JSON,
616 installed,
617 ModelValidationPhase::Input,
618 false,
619 )?;
620 DynamicRelationManager::new_canonical(self.db.inner_orm(), Arc::new(descriptor))
621 .count()
622 .await
623 .map_err(Error::from_orm)
624 }
625}