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;
9use type_bridge_orm::{
10 ProjectedBatchOperation, ProjectedBatchRow, ProjectedCrudExecutor, ProjectedManagerComparison,
11};
12
13use crate::__codegen::{
14 CompleteModel, EncodedCreate, EntityModel, FieldToken, HydrationCapability, IntoEncodedCreate,
15 Model, QueryValued, SubtypeRootModel,
16};
17use crate::entity_codec::{
18 hydrate_entity, lower_entity_create, map_validation_error, resolve_discovered_entity,
19 resolve_entity_authority,
20};
21use crate::error::{Error, ModelValidationPhase};
22use crate::hooks::{CrudOperation, HookRunner, LifecycleHook, ModelKind};
23use crate::projected_batch::{
24 checked_batch_ordinal, create_rows, delete_rows, encode_batch_create, execute_owned_delete,
25 execute_owned_things, prepare_batch, project_encoded_batch_create, reserved_binding_vec,
26 uses_successor_batch_runtime, validate_binding_row_count,
27};
28use crate::projected_codec::{materialize_projected, project_create};
29use crate::projected_filter::ProjectedEntityFilter;
30use crate::schema::Schema;
31use crate::{Database, Result};
32
33#[cfg(test)]
34mod tests;
35
36fn invalid_iid() -> Error {
37 Error::model_validation(
38 ModelValidationPhase::Input,
39 "invalid_iid",
40 vec!["iid".into()],
41 "IID is not canonical",
42 None,
43 )
44}
45
46fn schema_not_bound() -> Error {
47 Error::model_validation(
48 ModelValidationPhase::Input,
49 "schema_not_bound",
50 vec![],
51 "database is not schema-bound",
52 None,
53 )
54}
55
56pub(crate) async fn rehydrate_written_entity<M>(
59 manager: &DynamicEntityManager<'_>,
60 iid: &str,
61 id: &type_bridge_contract::id::TypeId,
62 installed: &type_bridge_orm::InstalledRuntimeProjection,
63) -> Result<M>
64where
65 M: crate::__codegen::CompleteModel,
66{
67 let row = manager
68 .get_by_iid_exact(iid)
69 .await
70 .map_err(Error::from_orm)?
71 .ok_or_else(|| {
72 Error::model_validation(
73 ModelValidationPhase::Hydration,
74 "missing_post_write_row",
75 vec!["iid".into()],
76 "written entity was not returned",
77 None,
78 )
79 })?;
80 let hydrated = hydrate_entity(row, id, installed)?;
81 M::materialize(&hydrated, &HydrationCapability::new())
82 .map_err(|error| map_validation_error(error, ModelValidationPhase::Hydration))
83}
84
85impl<S, M> EntitySubtypeManager<'_, S, M>
86where
87 S: Schema,
88 M: SubtypeRootModel<Schema = S> + EntityModel<Schema = S>,
89{
90 pub async fn get_by_iid(&self, _iid: &str) -> Result<Option<M::Subtypes>> {
93 if !is_canonical_thing_iid(_iid) {
94 return Err(invalid_iid());
95 }
96 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
97 let (_id, descriptor) = resolve_entity_authority(
98 M::TYPE_ID_JSON,
99 installed,
100 ModelValidationPhase::Input,
101 false,
102 )?;
103 let tx = self
104 .db
105 .inner_orm()
106 .transaction_context(TxType::Read)
107 .await
108 .map_err(Error::from_orm)?;
109 let manager =
110 DynamicEntityManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
111 let identity = match manager.discover_by_iid(_iid).await {
112 Ok(v) => v,
113 Err(e) => {
114 let _ = tx.close().await;
115 return Err(Error::from_orm(e));
116 }
117 };
118 let out = match identity {
119 None => None,
120 Some(identity) => {
121 let (child_id, child_descriptor) =
122 match resolve_discovered_entity(&identity.type_name, installed) {
123 Ok(v) => v,
124 Err(e) => {
125 let _ = tx.close().await;
126 return Err(e);
127 }
128 };
129 let child = DynamicEntityManager::with_canonical_transaction(
130 tx.clone(),
131 Arc::new(child_descriptor),
132 );
133 let row = match child.get_by_iid_exact(&identity.iid).await {
134 Ok(Some(v)) => v,
135 Ok(None) => {
136 let _ = tx.close().await;
137 return Err(Error::model_validation(
138 ModelValidationPhase::Hydration,
139 "missing_concrete_row",
140 vec!["iid".into()],
141 "discovered entity row is missing",
142 None,
143 ));
144 }
145 Err(e) => {
146 let _ = tx.close().await;
147 return Err(Error::from_orm(e));
148 }
149 };
150 let h = match hydrate_entity(row, &child_id, installed) {
151 Ok(v) => v,
152 Err(e) => {
153 let _ = tx.close().await;
154 return Err(e);
155 }
156 };
157 Some(
158 match M::__tb_dispatch_subtype(&h, &HydrationCapability::new()) {
159 Ok(v) => v,
160 Err(e) => {
161 let _ = tx.close().await;
162 return Err(map_validation_error(e, ModelValidationPhase::Hydration));
163 }
164 },
165 )
166 }
167 };
168 tx.close().await.map_err(Error::from_orm)?;
169 Ok(out)
170 }
171 pub async fn all(&self) -> Result<Vec<M::Subtypes>> {
175 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
176 let (_id, descriptor) = resolve_entity_authority(
177 M::TYPE_ID_JSON,
178 installed,
179 ModelValidationPhase::Input,
180 false,
181 )?;
182 let tx = self
183 .db
184 .inner_orm()
185 .transaction_context(TxType::Read)
186 .await
187 .map_err(Error::from_orm)?;
188 let manager =
189 DynamicEntityManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
190 let identities = match manager.discover_all().await {
191 Ok(v) => v,
192 Err(e) => {
193 let _ = tx.close().await;
194 return Err(Error::from_orm(e));
195 }
196 };
197 let mut out = Vec::with_capacity(identities.len());
198 for identity in identities {
199 let type_json = identity.type_name;
200 let (child_id, child_descriptor) =
201 match resolve_discovered_entity(&type_json, installed) {
202 Ok(v) => v,
203 Err(e) => {
204 let _ = tx.close().await;
205 return Err(e);
206 }
207 };
208 let child = DynamicEntityManager::with_canonical_transaction(
209 tx.clone(),
210 Arc::new(child_descriptor),
211 );
212 let row = match child.get_by_iid_exact(&identity.iid).await {
213 Ok(Some(v)) => v,
214 Ok(None) => {
215 let _ = tx.close().await;
216 return Err(Error::model_validation(
217 ModelValidationPhase::Hydration,
218 "missing_concrete_row",
219 vec!["iid".into()],
220 "discovered entity row is missing",
221 None,
222 ));
223 }
224 Err(e) => {
225 let _ = tx.close().await;
226 return Err(Error::from_orm(e));
227 }
228 };
229 let h = match hydrate_entity(row, &child_id, installed) {
230 Ok(v) => v,
231 Err(e) => {
232 let _ = tx.close().await;
233 return Err(e);
234 }
235 };
236 out.push(
237 match M::__tb_dispatch_subtype(&h, &HydrationCapability::new()) {
238 Ok(v) => v,
239 Err(e) => {
240 let _ = tx.close().await;
241 return Err(map_validation_error(e, ModelValidationPhase::Hydration));
242 }
243 },
244 );
245 }
246 tx.close().await.map_err(Error::from_orm)?;
247 Ok(out)
248 }
249 pub async fn count(&self) -> Result<u64> {
251 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
252 let (_id, descriptor) = resolve_entity_authority(
253 M::TYPE_ID_JSON,
254 installed,
255 ModelValidationPhase::Input,
256 false,
257 )?;
258 DynamicEntityManager::new_canonical(self.db.inner_orm(), Arc::new(descriptor))
259 .count()
260 .await
261 .map_err(Error::from_orm)
262 }
263}
264
265pub struct EntityManager<'db, S: Schema, M: EntityModel<Schema = S>> {
269 db: &'db Database<S>,
270 hooks: HookRunner,
271 marker: PhantomData<M>,
272}
273
274pub struct EntitySubtypeManager<
279 'db,
280 S: Schema,
281 M: SubtypeRootModel<Schema = S> + EntityModel<Schema = S>,
282> {
283 db: &'db Database<S>,
284 marker: PhantomData<M>,
285}
286
287impl<'db, S: Schema, M: SubtypeRootModel<Schema = S> + EntityModel<Schema = S>>
288 EntitySubtypeManager<'db, S, M>
289{
290 pub(crate) fn new(db: &'db Database<S>) -> Self {
291 Self {
292 db,
293 marker: PhantomData,
294 }
295 }
296}
297
298impl<'db, S, M> EntityManager<'db, S, M>
299where
300 S: Schema,
301 M: SubtypeRootModel<Schema = S> + EntityModel<Schema = S>,
302{
303 pub fn subtypes(&self) -> EntitySubtypeManager<'db, S, M> {
306 EntitySubtypeManager::new(self.db)
307 }
308}
309
310impl<'db, S: Schema, M: EntityModel<Schema = S>> Clone for EntityManager<'db, S, M> {
311 fn clone(&self) -> Self {
312 Self {
313 db: self.db,
314 hooks: self.hooks.clone(),
315 marker: PhantomData,
316 }
317 }
318}
319
320impl<S: Schema, M: EntityModel<Schema = S>> EntityManager<'_, S, M> {
321 pub(crate) fn new(db: &Database<S>) -> EntityManager<'_, S, M> {
322 EntityManager {
323 db,
324 hooks: HookRunner::default(),
325 marker: PhantomData,
326 }
327 }
328}
329
330impl<'db, S, M> EntityManager<'db, S, M>
331where
332 S: Schema,
333 M: EntityModel<Schema = S> + CompleteModel,
334{
335 pub fn filter(&self) -> Result<ProjectedEntityFilter<'db, S, M>> {
337 ProjectedEntityFilter::for_database(self.db)
338 }
339
340 pub fn where_<V>(
342 &self,
343 field: FieldToken<M, V>,
344 operator: ProjectedManagerComparison,
345 value: &V,
346 ) -> Result<ProjectedEntityFilter<'db, S, M>>
347 where
348 V: Model<Schema = S> + QueryValued,
349 {
350 self.filter()?.where_(field, operator, value)
351 }
352
353 fn successor_batches_enabled(&self) -> bool {
354 self.db
355 .installed_schema()
356 .is_some_and(|installed| uses_successor_batch_runtime(installed))
357 }
358
359 pub fn add_hook(&mut self, hook: Arc<dyn LifecycleHook>) -> &mut Self {
362 self.hooks.add(hook);
363 self
364 }
365
366 fn encode_hook_input(input: &M::Create) -> Result<EncodedCreate> {
367 input
368 .clone()
369 .into_encoded_create()
370 .map_err(|error| map_validation_error(error, ModelValidationPhase::Input))
371 }
372
373 pub async fn insert(&self, input: M::Create) -> Result<M> {
376 if !self.hooks.has_hooks() {
377 return self
378 .projected_write(input, CrudOperation::Insert, None)
379 .await;
380 }
381 let encoded = Self::encode_hook_input(&input)?;
382 let metadata = self
383 .hooks
384 .run_pre(
385 M::TYPE_ID_JSON,
386 ModelKind::Entity,
387 CrudOperation::Insert,
388 None,
389 Some(&encoded),
390 )
391 .await?;
392 let output = self
393 .projected_write(input, CrudOperation::Insert, None)
394 .await?;
395 self.hooks
396 .run_post(
397 M::TYPE_ID_JSON,
398 ModelKind::Entity,
399 CrudOperation::Insert,
400 Some(output.iid()),
401 Some(&encoded),
402 metadata,
403 )
404 .await;
405 Ok(output)
406 }
407 pub async fn put(&self, input: M::Create) -> Result<M> {
412 if !self.hooks.has_hooks() {
413 return self.projected_write(input, CrudOperation::Put, None).await;
414 }
415 let encoded = Self::encode_hook_input(&input)?;
416 let metadata = self
417 .hooks
418 .run_pre(
419 M::TYPE_ID_JSON,
420 ModelKind::Entity,
421 CrudOperation::Put,
422 None,
423 Some(&encoded),
424 )
425 .await?;
426 let output = self
427 .projected_write(input, CrudOperation::Put, None)
428 .await?;
429 self.hooks
430 .run_post(
431 M::TYPE_ID_JSON,
432 ModelKind::Entity,
433 CrudOperation::Put,
434 Some(output.iid()),
435 Some(&encoded),
436 metadata,
437 )
438 .await;
439 Ok(output)
440 }
441 pub async fn insert_many(&self, inputs: Vec<M::Create>) -> Result<Vec<M>> {
444 let successor = self.successor_batches_enabled();
445 if successor {
446 validate_binding_row_count(inputs.len())?;
447 }
448 if inputs.is_empty() && !successor {
449 return Ok(Vec::new());
450 }
451 if !self.hooks.has_hooks() {
452 return self.write_many(inputs, false).await;
453 }
454 if successor {
455 return self
456 .successor_write_many_with_hooks(
457 inputs,
458 CrudOperation::Insert,
459 ProjectedBatchOperation::Insert,
460 )
461 .await;
462 }
463 let encoded = inputs
464 .iter()
465 .map(Self::encode_hook_input)
466 .collect::<Result<Vec<_>>>()?;
467 let mut metadata = Vec::with_capacity(inputs.len());
468 for input in &encoded {
469 metadata.push(
470 self.hooks
471 .run_pre(
472 M::TYPE_ID_JSON,
473 ModelKind::Entity,
474 CrudOperation::Insert,
475 None,
476 Some(input),
477 )
478 .await?,
479 );
480 }
481 let outputs = self.write_many(inputs.clone(), false).await?;
482 for ((input, output), metadata) in encoded.iter().zip(&outputs).zip(metadata) {
483 self.hooks
484 .run_post(
485 M::TYPE_ID_JSON,
486 ModelKind::Entity,
487 CrudOperation::Insert,
488 Some(output.iid()),
489 Some(input),
490 metadata,
491 )
492 .await;
493 }
494 Ok(outputs)
495 }
496 pub async fn put_many(&self, inputs: Vec<M::Create>) -> Result<Vec<M>> {
500 let successor = self.successor_batches_enabled();
501 if successor {
502 validate_binding_row_count(inputs.len())?;
503 }
504 if inputs.is_empty() && !successor {
505 return Ok(Vec::new());
506 }
507 if !self.hooks.has_hooks() {
508 return self.write_many(inputs, true).await;
509 }
510 if successor {
511 return self
512 .successor_write_many_with_hooks(
513 inputs,
514 CrudOperation::Put,
515 ProjectedBatchOperation::Put,
516 )
517 .await;
518 }
519 let encoded = inputs
520 .iter()
521 .map(Self::encode_hook_input)
522 .collect::<Result<Vec<_>>>()?;
523 let mut metadata = Vec::with_capacity(inputs.len());
524 for input in &encoded {
525 metadata.push(
526 self.hooks
527 .run_pre(
528 M::TYPE_ID_JSON,
529 ModelKind::Entity,
530 CrudOperation::Put,
531 None,
532 Some(input),
533 )
534 .await?,
535 );
536 }
537 let outputs = self.write_many(inputs.clone(), true).await?;
538 for ((input, output), metadata) in encoded.iter().zip(&outputs).zip(metadata) {
539 self.hooks
540 .run_post(
541 M::TYPE_ID_JSON,
542 ModelKind::Entity,
543 CrudOperation::Put,
544 Some(output.iid()),
545 Some(input),
546 metadata,
547 )
548 .await;
549 }
550 Ok(outputs)
551 }
552 pub async fn update(&self, iid: &str, input: M::Create) -> Result<M> {
556 if !is_canonical_thing_iid(iid) {
557 return Err(invalid_iid());
558 }
559 if !self.hooks.has_hooks() {
560 return self
561 .projected_write(input, CrudOperation::Update, Some(iid))
562 .await;
563 }
564 let encoded = Self::encode_hook_input(&input)?;
565 let state = self
566 .hooks
567 .run_pre(
568 M::TYPE_ID_JSON,
569 ModelKind::Entity,
570 CrudOperation::Update,
571 Some(iid),
572 Some(&encoded),
573 )
574 .await?;
575 let output = self
576 .projected_write(input, CrudOperation::Update, Some(iid))
577 .await?;
578 self.hooks
579 .run_post(
580 M::TYPE_ID_JSON,
581 ModelKind::Entity,
582 CrudOperation::Update,
583 Some(output.iid()),
584 Some(&encoded),
585 state,
586 )
587 .await;
588 Ok(output)
589 }
590
591 pub async fn delete(&self, iid: &str) -> Result<()> {
593 if !is_canonical_thing_iid(iid) {
594 return Err(invalid_iid());
595 }
596 if !self.hooks.has_hooks() {
597 return self.projected_delete(iid).await;
598 }
599 let state = self
600 .hooks
601 .run_pre(
602 M::TYPE_ID_JSON,
603 ModelKind::Entity,
604 CrudOperation::Delete,
605 Some(iid),
606 None,
607 )
608 .await?;
609 self.projected_delete(iid).await?;
610 self.hooks
611 .run_post(
612 M::TYPE_ID_JSON,
613 ModelKind::Entity,
614 CrudOperation::Delete,
615 Some(iid),
616 None,
617 state,
618 )
619 .await;
620 Ok(())
621 }
622
623 pub async fn update_many(&self, inputs: Vec<(String, M::Create)>) -> Result<Vec<M>> {
627 let successor = self.successor_batches_enabled();
628 if successor {
629 validate_binding_row_count(inputs.len())?;
630 }
631 if inputs.is_empty() && !successor {
632 return Ok(Vec::new());
633 }
634 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
635 let (id, descriptor) = resolve_entity_authority(
636 M::TYPE_ID_JSON,
637 installed,
638 ModelValidationPhase::Input,
639 true,
640 )?;
641 if uses_successor_batch_runtime(installed) {
642 if !self.hooks.has_hooks() {
643 let rows = crate::projected_batch::update_rows(installed, &id, inputs)?;
644 let batch = prepare_batch(installed, id, ProjectedBatchOperation::Update, rows)?;
645 return execute_owned_things(self.db.inner_orm(), installed, &batch).await;
646 }
647 let mut hook_inputs = reserved_binding_vec(inputs.len())?;
648 let mut rows = reserved_binding_vec(inputs.len())?;
649 for (ordinal, (iid, input)) in inputs.into_iter().enumerate() {
650 let encoded = encode_batch_create(input, ordinal)?;
651 rows.push(ProjectedBatchRow::Update {
652 iid: iid.clone(),
653 replacement: project_encoded_batch_create(&encoded, &id, installed, ordinal)?,
654 });
655 hook_inputs.push((iid, encoded));
656 }
657 let batch = prepare_batch(installed, id, ProjectedBatchOperation::Update, rows)?;
658
659 let mut states = reserved_binding_vec(hook_inputs.len())?;
660 for (ordinal, (iid, encoded)) in hook_inputs.iter().enumerate() {
661 states.push(
662 self.hooks
663 .run_pre(
664 M::TYPE_ID_JSON,
665 ModelKind::Entity,
666 CrudOperation::Update,
667 Some(iid),
668 Some(encoded),
669 )
670 .await
671 .map_err(|error| {
672 error.with_projected_batch_row(checked_batch_ordinal(ordinal))
673 })?,
674 );
675 }
676
677 let outputs = execute_owned_things::<M>(self.db.inner_orm(), installed, &batch).await?;
678 for (((_, encoded), output), state) in hook_inputs.iter().zip(&outputs).zip(states) {
679 self.hooks
680 .run_post(
681 M::TYPE_ID_JSON,
682 ModelKind::Entity,
683 CrudOperation::Update,
684 Some(output.iid()),
685 Some(encoded),
686 state,
687 )
688 .await;
689 }
690 return Ok(outputs);
691 }
692 let mut prepared = Vec::with_capacity(inputs.len());
693 for (iid, input) in inputs {
694 if !is_canonical_thing_iid(&iid) {
695 return Err(invalid_iid());
696 }
697 let encoded = Self::encode_hook_input(&input)?;
698 let attributes = lower_entity_create(input, &id, installed)?;
699 prepared.push((iid, encoded, attributes));
700 }
701
702 let mut states = Vec::new();
703 if self.hooks.has_hooks() {
704 states.reserve(prepared.len());
705 for (iid, encoded, _) in &prepared {
706 states.push(
707 self.hooks
708 .run_pre(
709 M::TYPE_ID_JSON,
710 ModelKind::Entity,
711 CrudOperation::Update,
712 Some(iid),
713 Some(encoded),
714 )
715 .await?,
716 );
717 }
718 }
719
720 let tx = self
721 .db
722 .inner_orm()
723 .transaction_context(TxType::Write)
724 .await
725 .map_err(Error::from_orm)?;
726 let manager =
727 DynamicEntityManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
728 let mut outputs = Vec::with_capacity(prepared.len());
729 for (iid, _, attributes) in &prepared {
730 if let Err(error) = manager.update_exact(iid, attributes).await {
731 let _ = tx.rollback().await;
732 return Err(Error::from_orm(error));
733 }
734 match rehydrate_written_entity::<M>(&manager, iid, &id, installed).await {
735 Ok(output) => outputs.push(output),
736 Err(error) => {
737 let _ = tx.rollback().await;
738 return Err(error);
739 }
740 }
741 }
742 tx.commit().await.map_err(Error::from_orm)?;
743
744 if self.hooks.has_hooks() {
745 for (((_, encoded, _), output), state) in prepared.iter().zip(&outputs).zip(states) {
746 self.hooks
747 .run_post(
748 M::TYPE_ID_JSON,
749 ModelKind::Entity,
750 CrudOperation::Update,
751 Some(output.iid()),
752 Some(encoded),
753 state,
754 )
755 .await;
756 }
757 }
758 Ok(outputs)
759 }
760
761 pub async fn delete_many(&self, iids: &[String]) -> Result<()> {
765 let successor = self.successor_batches_enabled();
766 if successor {
767 validate_binding_row_count(iids.len())?;
768 }
769 if iids.is_empty() && !successor {
770 return Ok(());
771 }
772 if !successor && iids.iter().any(|iid| !is_canonical_thing_iid(iid)) {
773 return Err(invalid_iid());
774 }
775 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
776 let (id, descriptor) = resolve_entity_authority(
777 M::TYPE_ID_JSON,
778 installed,
779 ModelValidationPhase::Input,
780 true,
781 )?;
782
783 let batch = if successor {
784 Some(prepare_batch(
785 installed,
786 id.clone(),
787 ProjectedBatchOperation::Delete,
788 delete_rows(iids)?,
789 )?)
790 } else {
791 None
792 };
793 let mut states = if successor && self.hooks.has_hooks() {
794 reserved_binding_vec(iids.len())?
795 } else {
796 Vec::new()
797 };
798 if self.hooks.has_hooks() {
799 if !successor {
800 states.reserve(iids.len());
801 }
802 for (ordinal, iid) in iids.iter().enumerate() {
803 states.push(
804 self.hooks
805 .run_pre(
806 M::TYPE_ID_JSON,
807 ModelKind::Entity,
808 CrudOperation::Delete,
809 Some(iid),
810 None,
811 )
812 .await
813 .map_err(|error| {
814 if successor {
815 error.with_projected_batch_row(checked_batch_ordinal(ordinal))
816 } else {
817 error
818 }
819 })?,
820 );
821 }
822 }
823
824 if uses_successor_batch_runtime(installed) {
825 execute_owned_delete(
826 self.db.inner_orm(),
827 installed,
828 batch.as_ref().expect("successor batch was prepared"),
829 )
830 .await?;
831 } else {
832 let tx = self
833 .db
834 .inner_orm()
835 .transaction_context(TxType::Write)
836 .await
837 .map_err(Error::from_orm)?;
838 let manager =
839 DynamicEntityManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
840 for iid in iids {
841 if let Err(error) = manager.delete_by_iid_exact(iid).await {
842 let _ = tx.rollback().await;
843 return Err(Error::from_orm(error));
844 }
845 }
846 tx.commit().await.map_err(Error::from_orm)?;
847 }
848
849 if self.hooks.has_hooks() {
850 for (iid, state) in iids.iter().zip(states) {
851 self.hooks
852 .run_post(
853 M::TYPE_ID_JSON,
854 ModelKind::Entity,
855 CrudOperation::Delete,
856 Some(iid),
857 None,
858 state,
859 )
860 .await;
861 }
862 }
863 Ok(())
864 }
865
866 async fn projected_write(
867 &self,
868 input: M::Create,
869 operation: CrudOperation,
870 iid: Option<&str>,
871 ) -> Result<M> {
872 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
873 let (id, _descriptor) = resolve_entity_authority(
874 M::TYPE_ID_JSON,
875 installed,
876 ModelValidationPhase::Input,
877 true,
878 )?;
879 let create = project_create(input, &id, installed)?;
880 let tx = self
881 .db
882 .inner_orm()
883 .transaction_context(TxType::Write)
884 .await
885 .map_err(Error::from_orm)?;
886 let executor = ProjectedCrudExecutor::new(installed);
887 let projected = match operation {
888 CrudOperation::Insert => {
889 executor
890 .insert_entity_in_transaction_with_compatibility(&tx, &create)
891 .await
892 }
893 CrudOperation::Put => {
894 executor
895 .put_entity_in_transaction_with_compatibility(&tx, &create)
896 .await
897 }
898 CrudOperation::Update => {
899 executor
900 .update_entity_in_transaction_with_compatibility(
901 &tx,
902 iid.expect("projected entity update requires a checked IID"),
903 &create,
904 )
905 .await
906 }
907 CrudOperation::Delete => unreachable!("delete has no generated create payload"),
908 };
909 let projected = match projected {
910 Ok(value) => value,
911 Err(error) => {
912 let mapped = Error::from_projected_crud(error, ModelKind::Entity, Some(operation));
913 let _ = tx.rollback().await;
914 return Err(mapped);
915 }
916 };
917 let value = match materialize_projected(projected, installed) {
918 Ok(value) => value,
919 Err(error) => {
920 let _ = tx.rollback().await;
921 return Err(error);
922 }
923 };
924 tx.commit_classified()
925 .await
926 .map_err(|error| Error::from_orm(error.into_orm_error()))?;
927 Ok(value)
928 }
929
930 async fn projected_delete(&self, iid: &str) -> Result<()> {
931 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
932 let (id, _descriptor) = resolve_entity_authority(
933 M::TYPE_ID_JSON,
934 installed,
935 ModelValidationPhase::Input,
936 true,
937 )?;
938 let tx = self
939 .db
940 .inner_orm()
941 .transaction_context(TxType::Write)
942 .await
943 .map_err(Error::from_orm)?;
944 let result = ProjectedCrudExecutor::new(installed)
945 .delete_entity_by_iid_in_transaction_with_compatibility(&tx, &id, iid)
946 .await;
947 if let Err(error) = result {
948 let mapped =
949 Error::from_projected_crud(error, ModelKind::Entity, Some(CrudOperation::Delete));
950 let _ = tx.rollback().await;
951 return Err(mapped);
952 }
953 tx.commit_classified()
954 .await
955 .map_err(|error| Error::from_orm(error.into_orm_error()))
956 }
957
958 async fn successor_write_many_with_hooks(
959 &self,
960 inputs: Vec<M::Create>,
961 crud_operation: CrudOperation,
962 batch_operation: ProjectedBatchOperation,
963 ) -> Result<Vec<M>> {
964 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
965 let (id, _descriptor) = resolve_entity_authority(
966 M::TYPE_ID_JSON,
967 installed,
968 ModelValidationPhase::Input,
969 true,
970 )?;
971 let mut encoded = reserved_binding_vec(inputs.len())?;
972 let mut rows = reserved_binding_vec(inputs.len())?;
973 for (ordinal, input) in inputs.into_iter().enumerate() {
974 let input = encode_batch_create(input, ordinal)?;
975 rows.push(ProjectedBatchRow::Create(project_encoded_batch_create(
976 &input, &id, installed, ordinal,
977 )?));
978 encoded.push(input);
979 }
980 let batch = prepare_batch(installed, id, batch_operation, rows)?;
981
982 let mut metadata = reserved_binding_vec(encoded.len())?;
983 for (ordinal, input) in encoded.iter().enumerate() {
984 metadata.push(
985 self.hooks
986 .run_pre(
987 M::TYPE_ID_JSON,
988 ModelKind::Entity,
989 crud_operation,
990 None,
991 Some(input),
992 )
993 .await
994 .map_err(|error| {
995 error.with_projected_batch_row(checked_batch_ordinal(ordinal))
996 })?,
997 );
998 }
999
1000 let outputs = execute_owned_things::<M>(self.db.inner_orm(), installed, &batch).await?;
1001 for ((input, output), metadata) in encoded.iter().zip(&outputs).zip(metadata) {
1002 self.hooks
1003 .run_post(
1004 M::TYPE_ID_JSON,
1005 ModelKind::Entity,
1006 crud_operation,
1007 Some(output.iid()),
1008 Some(input),
1009 metadata,
1010 )
1011 .await;
1012 }
1013 Ok(outputs)
1014 }
1015
1016 async fn write_many(&self, inputs: Vec<M::Create>, put: bool) -> Result<Vec<M>> {
1017 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
1018 let (id, descriptor) = resolve_entity_authority(
1019 M::TYPE_ID_JSON,
1020 installed,
1021 ModelValidationPhase::Input,
1022 true,
1023 )?;
1024 if uses_successor_batch_runtime(installed) {
1025 let rows = create_rows(installed, &id, inputs)?;
1026 let operation = if put {
1027 ProjectedBatchOperation::Put
1028 } else {
1029 ProjectedBatchOperation::Insert
1030 };
1031 let batch = prepare_batch(installed, id, operation, rows)?;
1032 return execute_owned_things(self.db.inner_orm(), installed, &batch).await;
1033 }
1034 let mut lowered = Vec::with_capacity(inputs.len());
1035 for input in inputs {
1036 lowered.push(lower_entity_create(input, &id, installed)?);
1037 }
1038 let tx = self
1039 .db
1040 .inner_orm()
1041 .transaction_context(TxType::Write)
1042 .await
1043 .map_err(Error::from_orm)?;
1044 let manager =
1045 DynamicEntityManager::with_canonical_transaction(tx.clone(), Arc::new(descriptor));
1046 let iids = match if put {
1047 manager.put_many_exact(&lowered).await
1048 } else {
1049 manager.insert_many(&lowered).await
1050 } {
1051 Ok(v) if v.len() == lowered.len() => v,
1052 Ok(_) => {
1053 let _ = tx.rollback().await;
1054 return Err(Error::model_validation(
1055 ModelValidationPhase::Hydration,
1056 "iid_count_mismatch",
1057 vec!["iid".into()],
1058 "provider returned an unexpected IID count",
1059 None,
1060 ));
1061 }
1062 Err(e) => {
1063 let _ = tx.rollback().await;
1064 return Err(Error::from_orm(e));
1065 }
1066 };
1067 let mut out = Vec::with_capacity(iids.len());
1068 for iid in iids {
1069 let row = match manager.get_by_iid_exact(&iid).await {
1070 Ok(Some(r)) => r,
1071 Ok(None) => {
1072 let _ = tx.rollback().await;
1073 return Err(Error::model_validation(
1074 ModelValidationPhase::Hydration,
1075 "missing_post_write_row",
1076 vec!["iid".into()],
1077 "written entity was not returned",
1078 None,
1079 ));
1080 }
1081 Err(e) => {
1082 let _ = tx.rollback().await;
1083 return Err(Error::from_orm(e));
1084 }
1085 };
1086 let h = match hydrate_entity(row, &id, installed) {
1087 Ok(v) => v,
1088 Err(e) => {
1089 let _ = tx.rollback().await;
1090 return Err(e);
1091 }
1092 };
1093 let value = match M::materialize(&h, &HydrationCapability::new()) {
1094 Ok(v) => v,
1095 Err(e) => {
1096 let _ = tx.rollback().await;
1097 return Err(map_validation_error(e, ModelValidationPhase::Hydration));
1098 }
1099 };
1100 out.push(value);
1101 }
1102 tx.commit().await.map_err(Error::from_orm)?;
1103 Ok(out)
1104 }
1105 pub async fn count(&self) -> Result<u64> {
1107 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
1108 let (id, _descriptor) = resolve_entity_authority(
1109 M::TYPE_ID_JSON,
1110 installed,
1111 ModelValidationPhase::Input,
1112 true,
1113 )?;
1114 ProjectedCrudExecutor::new(installed)
1115 .count_entities_with_compatibility(self.db.inner_orm(), &id)
1116 .await
1117 .map_err(|error| Error::from_projected_crud(error, ModelKind::Entity, None))
1118 }
1119
1120 pub async fn get_by_iid(&self, iid: &str) -> Result<Option<M>> {
1123 if !is_canonical_thing_iid(iid) {
1124 return Err(invalid_iid());
1125 }
1126 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
1127 let (id, _descriptor) = resolve_entity_authority(
1128 M::TYPE_ID_JSON,
1129 installed,
1130 ModelValidationPhase::Input,
1131 true,
1132 )?;
1133 ProjectedCrudExecutor::new(installed)
1134 .get_entity_by_iid_with_compatibility(self.db.inner_orm(), &id, iid)
1135 .await
1136 .map_err(|error| Error::from_projected_crud(error, ModelKind::Entity, None))?
1137 .map(|projected| materialize_projected(projected, installed))
1138 .transpose()
1139 }
1140
1141 pub async fn all(&self) -> Result<Vec<M>> {
1144 let installed = self.db.installed_schema().ok_or_else(schema_not_bound)?;
1145 let (id, descriptor) = resolve_entity_authority(
1146 M::TYPE_ID_JSON,
1147 installed,
1148 ModelValidationPhase::Input,
1149 true,
1150 )?;
1151 let rows =
1152 DynamicEntityManager::new_canonical(self.db.inner_orm(), Arc::new(descriptor.clone()))
1153 .all_exact()
1154 .await
1155 .map_err(Error::from_orm)?;
1156 rows.into_iter()
1157 .map(|r| {
1158 let h = hydrate_entity(r, &id, installed)?;
1159 M::materialize(&h, &HydrationCapability::new())
1160 .map_err(|e| map_validation_error(e, ModelValidationPhase::Hydration))
1161 })
1162 .collect()
1163 }
1164}