Skip to main content

sofie_db/
forgotten.rs

1use sofie_core::{Result, SqlValue, Uuid};
2
3use crate::{Plan, Read, Step, query_values};
4
5const SCHEMA: &str = "sofie";
6const TABLE: &str = "forgotten_principals";
7
8/// Erases the framework's own rows of a principal, such as product events, and records it for
9/// [`unless_forgotten`]; planned first, so no write racing the erasure outlives it.
10pub fn forget_principal(plan: &mut Plan<'_>, principal: Uuid) {
11    remember(plan, principal);
12    #[cfg(feature = "product-reports")]
13    if let Some(sql) = plan.dialect().product_events() {
14        let params = vec![SqlValue::Uuid(principal)];
15        plan.push(Step::statement(sql.forget, params, None));
16    }
17}
18
19pub async fn forgotten(read: &Read, principal: Uuid) -> Result<bool> {
20    let sql = select(read.dialect());
21    let rows = query_values(read.db.driver(), &sql, &[SqlValue::Uuid(principal)]).await?;
22    Ok(!rows.is_empty())
23}
24
25/// The commit conflicts if the principal was forgotten since it was read; the retry sees it.
26pub fn unless_forgotten(plan: &mut Plan<'_>, principal: Uuid) {
27    act_as_framework(plan);
28    lock(plan, plan.dialect().shared_key_lock(), principal);
29    let sql = select(plan.dialect());
30    plan.push(Step::statement(
31        sql,
32        vec![SqlValue::Uuid(principal)],
33        Some(0),
34    ));
35}
36
37/// Locked before the erasure's deletes, so a write holding the shared lock is committed and
38/// seen by them, and a later one waits and reads the record.
39fn remember(plan: &mut Plan<'_>, principal: Uuid) {
40    act_as_framework(plan);
41    lock(plan, plan.dialect().key_lock(), principal);
42    let dialect = plan.dialect();
43    let sql = format!(
44        "INSERT INTO {} (principal) VALUES ({}) ON CONFLICT DO NOTHING",
45        dialect.qualified(SCHEMA, TABLE),
46        dialect.placeholder(1),
47    );
48    plan.push(Step::statement(sql, vec![SqlValue::Uuid(principal)], None));
49}
50
51fn select(dialect: &dyn crate::Dialect) -> String {
52    format!(
53        "SELECT principal FROM {} WHERE principal = {}",
54        dialect.qualified(SCHEMA, TABLE),
55        dialect.placeholder(1),
56    )
57}
58
59fn lock(plan: &mut Plan<'_>, lock: Option<&'static str>, principal: Uuid) {
60    if let Some(lock) = lock {
61        let key = SqlValue::Text(format!("{SCHEMA}.{TABLE}:{principal}"));
62        plan.push(Step::statement(lock, vec![key], None));
63    }
64}
65
66#[cfg_attr(not(feature = "module-roles"), expect(unused_variables))]
67fn act_as_framework(plan: &mut Plan<'_>) {
68    #[cfg(feature = "module-roles")]
69    plan.act_as(crate::outcome::FRAMEWORK_MODULE);
70}