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