Skip to main content

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}