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