systemprompt_database/repository/ownership.rs
1//! One implementation of [`OwnerReassignment`] for every domain that owns
2//! user-keyed rows.
3//!
4//! [`owner_reassignment!`](crate::owner_reassignment) declares a repository
5//! struct and its [`OwnerReassignment`] impl from a list of per-table
6//! statements. Each statement stays a compile-time `sqlx::query!` expanded
7//! in the declaring crate, so its offline cache entry lives with the crate
8//! that owns the table. The generated impl runs every statement in one
9//! transaction and reports the rows each one touched, in declaration order.
10//!
11//! An `update` statement binds the source user as `$1` and the target user
12//! as `$2`; a `delete` statement binds only the source user as `$1`, for
13//! rows that are credentials minted for the old user and must not be rebound.
14//!
15//! ```ignore
16//! systemprompt_database::owner_reassignment! {
17//! pub struct FilesOwnerReassignment;
18//! domain = "files";
19//! update "files" => "UPDATE files SET user_id = $2 WHERE user_id = $1";
20//! }
21//! ```
22//!
23//! [`OwnerReassignment`]: systemprompt_traits::OwnerReassignment
24//!
25//! Copyright (c) systemprompt.io — Business Source License 1.1.
26//! See <https://systemprompt.io> for licensing details.
27
28#[macro_export]
29macro_rules! owner_reassignment {
30 (
31 $vis:vis struct $name:ident;
32 domain = $domain:literal;
33 $( $kind:ident $table:literal => $sql:literal; )+
34 ) => {
35 #[derive(Debug, Clone)]
36 $vis struct $name {
37 write_pool: ::std::sync::Arc<::sqlx::PgPool>,
38 }
39
40 impl $name {
41 pub fn new(db: &$crate::DbPool) -> Self {
42 Self {
43 write_pool: db.write_pool(),
44 }
45 }
46 }
47
48 #[::async_trait::async_trait]
49 impl ::systemprompt_traits::OwnerReassignment for $name {
50 fn domain(&self) -> &'static str {
51 $domain
52 }
53
54 async fn reassign_owner(
55 &self,
56 from: &::systemprompt_identifiers::UserId,
57 to: &::systemprompt_identifiers::UserId,
58 ) -> ::std::result::Result<
59 ::systemprompt_traits::ReassignedRows,
60 ::systemprompt_traits::RepositoryError,
61 > {
62 let mut tx = self.write_pool.begin().await?;
63 let mut tables = ::std::vec::Vec::new();
64 $(
65 let rows = $crate::owner_reassignment!(@step $kind tx from to $sql);
66 tables.push(($table, rows));
67 )+
68 tx.commit().await?;
69 Ok(::systemprompt_traits::ReassignedRows { tables })
70 }
71 }
72 };
73 (@step update $tx:ident $from:ident $to:ident $sql:literal) => {
74 ::sqlx::query!($sql, $from.as_str(), $to.as_str())
75 .execute(&mut *$tx)
76 .await?
77 .rows_affected()
78 };
79 (@step delete $tx:ident $from:ident $to:ident $sql:literal) => {
80 ::sqlx::query!($sql, $from.as_str())
81 .execute(&mut *$tx)
82 .await?
83 .rows_affected()
84 };
85}