1#![deny(missing_docs)]
2use std::marker::PhantomData;
5use std::sync::Arc;
6
7use type_bridge_contract::id::{TypeId, is_canonical_thing_iid};
8use type_bridge_orm::_descriptor::{EntityDescriptor, RelationDescriptor};
9use type_bridge_orm::_manager::{DynamicEntityManager, DynamicRelationManager};
10use type_bridge_orm::_registry::DescriptorRegistry;
11use type_bridge_orm::session::backend::TxType;
12use type_bridge_orm::session::context::TransactionContext;
13use type_bridge_orm::{DynamicAttributeMap, DynamicRolePlayerInput, InstalledRuntimeProjection};
14
15use crate::__codegen::{CompleteModel, EntityModel, HydrationCapability, RelationModel};
16use crate::entity_codec::{
17 hydrate_entity, lower_entity_create, map_validation_error, resolve_entity_authority,
18};
19use crate::entity_manager::rehydrate_written_entity;
20use crate::error::{Error, ModelValidationPhase};
21use crate::relation_codec::{hydrate_relation, lower_relation_create, resolve_relation_authority};
22use crate::relation_manager::{one_coalesced_row, rehydrate_written_relation};
23use crate::schema::Schema;
24use crate::{Database, Result};
25
26#[cfg(test)]
27mod tests;
28
29fn invalid_iid() -> Error {
30 Error::model_validation(
31 ModelValidationPhase::Input,
32 "invalid_iid",
33 vec!["iid".into()],
34 "IID is not canonical",
35 None,
36 )
37}
38
39fn schema_not_bound() -> Error {
40 Error::model_validation(
41 ModelValidationPhase::Input,
42 "schema_not_bound",
43 vec![],
44 "database is not schema-bound",
45 None,
46 )
47}
48
49pub struct ReadTransaction<'db, S: Schema> {
55 tx: TransactionContext,
56 db: &'db Database<S>,
57 installed: Arc<InstalledRuntimeProjection>,
58 registry: Arc<DescriptorRegistry>,
59}
60
61impl<S: Schema> std::fmt::Debug for ReadTransaction<'_, S> {
62 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 formatter
64 .debug_struct("ReadTransaction")
65 .field("database", &self.db.database_name())
66 .finish_non_exhaustive()
67 }
68}
69
70impl<'db, S: Schema> ReadTransaction<'db, S> {
71 pub(crate) async fn open(db: &'db Database<S>) -> Result<Self> {
72 let installed = Arc::clone(db.installed_schema().ok_or_else(schema_not_bound)?);
73 let registry = Arc::clone(db.match_registry().ok_or_else(schema_not_bound)?);
74 let tx = db
75 .inner_orm()
76 .transaction_context(TxType::Read)
77 .await
78 .map_err(Error::from_orm)?;
79 Ok(Self {
80 tx,
81 db,
82 installed,
83 registry,
84 })
85 }
86
87 #[must_use]
89 pub fn query(&self) -> crate::query::QuerySession<'_, S> {
90 crate::query::QuerySession::borrowed(&self.installed, Arc::clone(&self.registry), &self.tx)
91 }
92
93 pub async fn close(self) -> Result<()> {
95 self.tx.close().await.map_err(Error::from_orm)
96 }
97}
98
99pub struct WriteTransaction<'db, S: Schema> {
108 tx: TransactionContext,
109 db: &'db Database<S>,
110}
111
112impl<S: Schema> std::fmt::Debug for WriteTransaction<'_, S> {
113 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 formatter
115 .debug_struct("WriteTransaction")
116 .field("database", &self.db.database_name())
117 .finish_non_exhaustive()
118 }
119}
120
121impl<'db, S: Schema> WriteTransaction<'db, S> {
122 pub(crate) async fn open(db: &'db Database<S>) -> Result<WriteTransaction<'db, S>> {
123 db.installed_schema().ok_or_else(schema_not_bound)?;
124 let tx = db
125 .inner_orm()
126 .transaction_context(TxType::Write)
127 .await
128 .map_err(Error::from_orm)?;
129 Ok(WriteTransaction { tx, db })
130 }
131
132 pub fn entities<M>(&self) -> TransactionEntityManager<'_, S, M>
134 where
135 M: EntityModel<Schema = S>,
136 {
137 TransactionEntityManager {
138 transaction: self,
139 marker: PhantomData,
140 }
141 }
142
143 pub fn relations<M>(&self) -> TransactionRelationManager<'_, S, M>
145 where
146 M: RelationModel<Schema = S>,
147 {
148 TransactionRelationManager {
149 transaction: self,
150 marker: PhantomData,
151 }
152 }
153
154 pub async fn commit(self) -> Result<()> {
156 self.tx.commit().await.map_err(Error::from_orm)
157 }
158
159 pub async fn rollback(self) -> Result<()> {
161 self.tx.rollback().await.map_err(Error::from_orm)
162 }
163
164 fn installed(&self) -> Result<&InstalledRuntimeProjection> {
165 self.db
166 .installed_schema()
167 .map(Arc::as_ref)
168 .ok_or_else(schema_not_bound)
169 }
170}
171
172pub struct TransactionEntityManager<'t, S: Schema, M: EntityModel<Schema = S>> {
177 transaction: &'t WriteTransaction<'t, S>,
178 marker: PhantomData<M>,
179}
180
181impl<'t, S: Schema, M: EntityModel<Schema = S>> Copy for TransactionEntityManager<'t, S, M> {}
182impl<'t, S: Schema, M: EntityModel<Schema = S>> Clone for TransactionEntityManager<'t, S, M> {
183 fn clone(&self) -> Self {
184 *self
185 }
186}
187
188impl<S, M> TransactionEntityManager<'_, S, M>
189where
190 S: Schema,
191 M: EntityModel<Schema = S> + CompleteModel,
192{
193 fn exact(
194 &self,
195 ) -> Result<(
196 TypeId,
197 &InstalledRuntimeProjection,
198 DynamicEntityManager<'static>,
199 )> {
200 let installed = self.transaction.installed()?;
201 let (id, descriptor): (TypeId, EntityDescriptor) = resolve_entity_authority(
202 M::TYPE_ID_JSON,
203 installed,
204 ModelValidationPhase::Input,
205 true,
206 )?;
207 let manager = DynamicEntityManager::with_canonical_transaction(
208 self.transaction.tx.clone(),
209 Arc::new(descriptor),
210 );
211 Ok((id, installed, manager))
212 }
213
214 pub async fn insert(&self, input: M::Create) -> Result<M> {
217 let (id, installed, manager) = self.exact()?;
218 let attributes = lower_entity_create(input, &id, installed)?;
219 let iid = manager.insert(&attributes).await.map_err(Error::from_orm)?;
220 rehydrate_written_entity(&manager, &iid, &id, installed).await
221 }
222
223 pub async fn put(&self, input: M::Create) -> Result<M> {
227 let (id, installed, manager) = self.exact()?;
228 let attributes = lower_entity_create(input, &id, installed)?;
229 let iid = manager
230 .put_exact(&attributes)
231 .await
232 .map_err(Error::from_orm)?;
233 rehydrate_written_entity(&manager, &iid, &id, installed).await
234 }
235
236 pub async fn insert_many(&self, inputs: Vec<M::Create>) -> Result<Vec<M>> {
239 self.write_many(inputs, false).await
240 }
241
242 pub async fn put_many(&self, inputs: Vec<M::Create>) -> Result<Vec<M>> {
246 self.write_many(inputs, true).await
247 }
248
249 async fn write_many(&self, inputs: Vec<M::Create>, put: bool) -> Result<Vec<M>> {
250 if inputs.is_empty() {
251 return Ok(Vec::new());
252 }
253 let (id, installed, manager) = self.exact()?;
254 let mut lowered = Vec::with_capacity(inputs.len());
255 for input in inputs {
256 lowered.push(lower_entity_create(input, &id, installed)?);
257 }
258 let iids = if put {
259 manager.put_many_exact(&lowered).await
260 } else {
261 manager.insert_many(&lowered).await
262 }
263 .map_err(Error::from_orm)?;
264 if iids.len() != lowered.len() {
265 return Err(Error::model_validation(
266 ModelValidationPhase::Hydration,
267 "iid_count_mismatch",
268 vec!["iid".into()],
269 "provider returned an unexpected IID count",
270 None,
271 ));
272 }
273 let mut out = Vec::with_capacity(iids.len());
274 for iid in iids {
275 out.push(rehydrate_written_entity(&manager, &iid, &id, installed).await?);
276 }
277 Ok(out)
278 }
279
280 pub async fn update(&self, iid: &str, input: M::Create) -> Result<M> {
283 if !is_canonical_thing_iid(iid) {
284 return Err(invalid_iid());
285 }
286 let (id, installed, manager) = self.exact()?;
287 let attributes = lower_entity_create(input, &id, installed)?;
288 manager
289 .update_exact(iid, &attributes)
290 .await
291 .map_err(Error::from_orm)?;
292 rehydrate_written_entity(&manager, iid, &id, installed).await
293 }
294
295 pub async fn delete(&self, iid: &str) -> Result<()> {
298 if !is_canonical_thing_iid(iid) {
299 return Err(invalid_iid());
300 }
301 let (_id, _installed, manager) = self.exact()?;
302 manager
303 .delete_by_iid_exact(iid)
304 .await
305 .map_err(Error::from_orm)
306 }
307
308 pub async fn get_by_iid(&self, iid: &str) -> Result<Option<M>> {
311 if !is_canonical_thing_iid(iid) {
312 return Err(invalid_iid());
313 }
314 let (id, installed, manager) = self.exact()?;
315 match manager
316 .get_by_iid_exact(iid)
317 .await
318 .map_err(Error::from_orm)?
319 {
320 None => Ok(None),
321 Some(row) => {
322 let hydrated = hydrate_entity(row, &id, installed)?;
323 M::materialize(&hydrated, &HydrationCapability::new())
324 .map(Some)
325 .map_err(|error| map_validation_error(error, ModelValidationPhase::Hydration))
326 }
327 }
328 }
329
330 pub async fn all(&self) -> Result<Vec<M>> {
333 let (id, installed, manager) = self.exact()?;
334 let rows = manager.all_exact().await.map_err(Error::from_orm)?;
335 rows.into_iter()
336 .map(|row| {
337 let hydrated = hydrate_entity(row, &id, installed)?;
338 M::materialize(&hydrated, &HydrationCapability::new())
339 .map_err(|error| map_validation_error(error, ModelValidationPhase::Hydration))
340 })
341 .collect()
342 }
343
344 pub async fn count(&self) -> Result<u64> {
346 let (_id, _installed, manager) = self.exact()?;
347 manager.count_exact().await.map_err(Error::from_orm)
348 }
349}
350
351pub struct TransactionRelationManager<'t, S: Schema, M: RelationModel<Schema = S>> {
356 transaction: &'t WriteTransaction<'t, S>,
357 marker: PhantomData<M>,
358}
359
360impl<'t, S: Schema, M: RelationModel<Schema = S>> Copy for TransactionRelationManager<'t, S, M> {}
361impl<'t, S: Schema, M: RelationModel<Schema = S>> Clone for TransactionRelationManager<'t, S, M> {
362 fn clone(&self) -> Self {
363 *self
364 }
365}
366
367impl<S, M> TransactionRelationManager<'_, S, M>
368where
369 S: Schema,
370 M: RelationModel<Schema = S> + CompleteModel,
371{
372 fn exact(
373 &self,
374 ) -> Result<(
375 TypeId,
376 &InstalledRuntimeProjection,
377 DynamicRelationManager<'static>,
378 )> {
379 let installed = self.transaction.installed()?;
380 let (id, descriptor): (TypeId, RelationDescriptor) = resolve_relation_authority(
381 M::TYPE_ID_JSON,
382 installed,
383 ModelValidationPhase::Input,
384 true,
385 )?;
386 let manager = DynamicRelationManager::with_canonical_transaction(
387 self.transaction.tx.clone(),
388 Arc::new(descriptor),
389 );
390 Ok((id, installed, manager))
391 }
392
393 pub async fn insert(&self, input: M::Create) -> Result<M> {
397 let (id, installed, manager) = self.exact()?;
398 let prepared = lower_relation_create(input, &id, installed)?;
399 let iid = manager
400 .insert(&prepared.attributes, &prepared.role_players)
401 .await
402 .map_err(Error::from_orm)?;
403 rehydrate_written_relation(&manager, &iid, &id, installed).await
404 }
405
406 pub async fn put(&self, input: M::Create) -> Result<M> {
410 let (id, installed, manager) = self.exact()?;
411 let prepared = lower_relation_create(input, &id, installed)?;
412 let iid = manager
413 .put_exact(&prepared.attributes, &prepared.role_players)
414 .await
415 .map_err(Error::from_orm)?;
416 rehydrate_written_relation(&manager, &iid, &id, installed).await
417 }
418
419 pub async fn insert_many(&self, inputs: Vec<M::Create>) -> Result<Vec<M>> {
422 self.write_many(inputs, false).await
423 }
424
425 pub async fn put_many(&self, inputs: Vec<M::Create>) -> Result<Vec<M>> {
429 self.write_many(inputs, true).await
430 }
431
432 async fn write_many(&self, inputs: Vec<M::Create>, put: bool) -> Result<Vec<M>> {
433 if inputs.is_empty() {
434 return Ok(Vec::new());
435 }
436 let (id, installed, manager) = self.exact()?;
437 let mut lowered: Vec<(DynamicAttributeMap, Vec<DynamicRolePlayerInput>)> =
438 Vec::with_capacity(inputs.len());
439 for input in inputs {
440 let prepared = lower_relation_create(input, &id, installed)?;
441 lowered.push((prepared.attributes, prepared.role_players));
442 }
443 let iids = if put {
444 manager.put_many_exact(&lowered).await
445 } else {
446 manager.insert_many(&lowered).await
447 }
448 .map_err(Error::from_orm)?;
449 if iids.len() != lowered.len() {
450 return Err(Error::model_validation(
451 ModelValidationPhase::Hydration,
452 "iid_count_mismatch",
453 vec!["iid".into()],
454 "provider returned an unexpected IID count",
455 None,
456 ));
457 }
458 let mut out = Vec::with_capacity(iids.len());
459 for iid in iids {
460 out.push(rehydrate_written_relation(&manager, &iid, &id, installed).await?);
461 }
462 Ok(out)
463 }
464
465 pub async fn update(&self, iid: &str, input: M::Create) -> Result<M> {
469 if !is_canonical_thing_iid(iid) {
470 return Err(invalid_iid());
471 }
472 let (id, installed, manager) = self.exact()?;
473 let prepared = lower_relation_create(input, &id, installed)?;
474 manager
475 .update_exact(iid, &prepared.attributes, &prepared.role_players)
476 .await
477 .map_err(Error::from_orm)?;
478 rehydrate_written_relation(&manager, iid, &id, installed).await
479 }
480
481 pub async fn delete(&self, iid: &str) -> Result<()> {
484 if !is_canonical_thing_iid(iid) {
485 return Err(invalid_iid());
486 }
487 let (_id, _installed, manager) = self.exact()?;
488 manager
489 .delete_by_iid_exact(iid)
490 .await
491 .map_err(Error::from_orm)
492 }
493
494 pub async fn get_by_iid(&self, iid: &str) -> Result<Option<M>> {
497 if !is_canonical_thing_iid(iid) {
498 return Err(invalid_iid());
499 }
500 let (id, installed, manager) = self.exact()?;
501 let rows = manager
502 .get_by_iid_exact(iid)
503 .await
504 .map_err(Error::from_orm)?;
505 match one_coalesced_row(rows)? {
506 None => Ok(None),
507 Some(row) => {
508 let hydrated = hydrate_relation(row, &id, installed)?;
509 M::materialize(&hydrated, &HydrationCapability::new())
510 .map(Some)
511 .map_err(|error| map_validation_error(error, ModelValidationPhase::Hydration))
512 }
513 }
514 }
515
516 pub async fn all(&self) -> Result<Vec<M>> {
519 let (id, installed, manager) = self.exact()?;
520 let rows = manager.all_exact().await.map_err(Error::from_orm)?;
521 rows.into_iter()
522 .map(|row| {
523 let hydrated = hydrate_relation(row, &id, installed)?;
524 M::materialize(&hydrated, &HydrationCapability::new())
525 .map_err(|error| map_validation_error(error, ModelValidationPhase::Hydration))
526 })
527 .collect()
528 }
529
530 pub async fn count(&self) -> Result<u64> {
532 let (_id, _installed, manager) = self.exact()?;
533 manager.count_exact().await.map_err(Error::from_orm)
534 }
535}