arcature_data/query/crud.rs
1//! CRUD operations over SeaORM `ActiveModel`s, with explicit `&Db` ownership.
2//!
3//! Each function takes the `&Db` handle explicitly (AGENTS.md §20; PROGRAM.md
4//! AP2.1-6). They delegate to SeaORM's `ActiveModelTrait` (`insert` / `update` /
5//! `delete`) over `db.orm()` — Arcature does not reimplement the mutation
6//! engine. There is no hidden global pool and no automatic transaction
7//! wrapping; a caller that needs atomicity wraps these in [`crate::Transaction`].
8
9use arcature_db::Db;
10use arcature_db::sea_orm;
11use arcature_db::sea_orm::{ActiveModelTrait, EntityTrait};
12
13use crate::error::DataError;
14
15/// The row model of an `ActiveModel`'s entity. `A::Entity` is the SeaORM
16/// `Entity`; its `Model` is the row type returned by insert/update/delete.
17type Row<A> = <<A as sea_orm::ActiveModelTrait>::Entity as EntityTrait>::Model;
18
19/// Insert a new row from an `ActiveModel`. Returns the inserted model with
20/// database-generated fields (auto-increment PKs, defaults) populated.
21///
22/// # Errors
23///
24/// Returns [`DataError::Database`] if the insert fails (e.g. a uniqueness
25/// constraint violation surfaces as a SeaORM `DbErr`).
26pub async fn insert<A>(db: &Db, active: A) -> Result<Row<A>, DataError>
27where
28 A: ActiveModelTrait + sea_orm::ActiveModelBehavior + Send,
29 <A::Entity as EntityTrait>::Model: sea_orm::IntoActiveModel<A>,
30{
31 active.insert(db.orm()).await.map_err(DataError::from)
32}
33
34/// Update an existing row from an `ActiveModel`. Returns the updated model.
35///
36/// # Errors
37///
38/// Returns [`DataError::Database`] if the update fails.
39pub async fn update<A>(db: &Db, active: A) -> Result<Row<A>, DataError>
40where
41 A: ActiveModelTrait + sea_orm::ActiveModelBehavior + Send,
42 <A::Entity as EntityTrait>::Model: sea_orm::IntoActiveModel<A>,
43{
44 active.update(db.orm()).await.map_err(DataError::from)
45}
46
47/// Delete an existing row from its `ActiveModel`. Returns a
48/// [`sea_orm::DeleteResult`] carrying the number of affected rows.
49///
50/// # Errors
51///
52/// Returns [`DataError::Database`] if the delete fails.
53pub async fn delete<A>(db: &Db, active: A) -> Result<sea_orm::DeleteResult, DataError>
54where
55 A: ActiveModelTrait + sea_orm::ActiveModelBehavior + Send,
56 <A::Entity as EntityTrait>::Model: sea_orm::IntoActiveModel<A>,
57{
58 active.delete(db.orm()).await.map_err(DataError::from)
59}
60
61/// Find a single row by its primary key.
62///
63/// This delegates to SeaORM's `Entity::find_by_id` over the explicit `&Db`.
64/// It is the typed primary-key lookup path; for a route-key lookup the
65/// application composes this with the `arcature` facade's `RouteModel`
66/// binding (the facade owns `RouteModel`; this crate does not depend on it).
67///
68/// # Errors
69///
70/// Returns [`DataError::Database`] if the underlying query fails. A missing
71/// row is `Ok(None)`, not an error — the caller decides whether that is a
72/// 404 (PROGRAM.md AP2.1-6: "No `Post::find(id).await?` that requires a hidden
73/// global"; this variant is explicit-ownership and returns `Option`).
74pub async fn find_by_pk<E, P>(db: &Db, pk: P) -> Result<Option<E::Model>, DataError>
75where
76 E: sea_orm::EntityTrait,
77 P: Into<<E::PrimaryKey as sea_orm::PrimaryKeyTrait>::ValueType> + Send,
78{
79 E::find_by_id(pk)
80 .one(db.orm())
81 .await
82 .map_err(DataError::from)
83}