Skip to main content

sofie_db/
flags.rs

1use std::fmt::Write as _;
2
3use sofie_core::{Cast, Result, SqlValue};
4
5use crate::behavior::stored;
6use crate::requested::stored_mut;
7use crate::{Aggregate, Behavior, Column, Engine, Field, Plan, Query, Source, Step, Uses, Write};
8
9pub struct Active;
10
11impl Behavior for Active {
12    fn columns(&self) -> &'static [Field] {
13        const { &[Field::new("is_active", bool::COLUMNS, false)] }
14    }
15
16    fn written(&self, _write: &Write<'_>, values: &mut [SqlValue]) {
17        if values[0] == SqlValue::Null {
18            values[0] = SqlValue::Bool(true);
19        }
20    }
21}
22
23pub trait Activates: Aggregate {
24    const IS_ACTIVE: Column<Self, bool> = Column::new("is_active");
25
26    fn activate(&mut self) {
27        *stored_mut::<Self>(self, "is_active") = SqlValue::Bool(true);
28    }
29
30    fn deactivate(&mut self) {
31        *stored_mut::<Self>(self, "is_active") = SqlValue::Bool(false);
32    }
33
34    fn is_active(&self) -> bool {
35        stored::<Self>(self, "is_active") != Some(&SqlValue::Bool(false))
36    }
37}
38
39impl<A: Uses<Active>> Activates for A {}
40
41#[must_use]
42pub fn active<T: Source>(query: Query<T>) -> Query<T>
43where
44    T::Base: Activates,
45{
46    query.where_(<T::Base as Activates>::IS_ACTIVE.eq(true))
47}
48
49/// Making one the default clears it on the owner's others in the same commit.
50pub struct Default;
51
52impl Behavior for Default {
53    fn columns(&self) -> &'static [Field] {
54        const { &[Field::new("is_default", bool::COLUMNS, false)] }
55    }
56
57    fn written(&self, _write: &Write<'_>, values: &mut [SqlValue]) {
58        if values[0] == SqlValue::Null {
59            values[0] = SqlValue::Bool(false);
60        }
61    }
62
63    fn planned(&self, write: &Write<'_>, plan: &mut Plan<'_>) -> Result<()> {
64        let flag = Some(&SqlValue::Bool(true));
65        if write.value("is_default") != flag || write.loaded("is_default") == flag {
66            return Ok(());
67        }
68        let dialect = plan.dialect();
69        let owner = write
70            .table
71            .owner
72            .map(|owner| write.value(owner).cloned().unwrap_or(SqlValue::Null));
73        if dialect.engine() == Engine::Postgres {
74            plan.push(one_default_at_a_time(write, owner.as_ref()));
75        }
76        let mut params = vec![SqlValue::Uuid(write.id)];
77        let mut sql = format!(
78            "UPDATE {table} SET {flag} = FALSE, {version} = {version} + 1 \
79             WHERE {flag} AND {id} <> {first}",
80            table = write.table.qualified(dialect),
81            flag = dialect.quote("is_default"),
82            version = dialect.quote("version"),
83            id = dialect.quote("id"),
84            first = dialect.placeholder(1),
85        );
86        if let (Some(column), Some(owner)) = (write.table.owner, owner) {
87            params.push(owner);
88            let _ = write!(
89                sql,
90                " AND {} = {}",
91                dialect.quote(column),
92                dialect.placeholder(2)
93            );
94        }
95        plan.push(Step::statement(sql, params, None));
96        #[cfg(feature = "cache")]
97        plan.changed(crate::ChangedRecord {
98            module: write.table.schema,
99            aggregate: write.table.name,
100            id: None,
101        });
102        Ok(())
103    }
104}
105
106/// Under READ COMMITTED two commits clearing each other's defaults miss the one the other sets,
107/// so both would stay; SQLite's single writer already serializes them.
108fn one_default_at_a_time(write: &Write<'_>, owner: Option<&SqlValue>) -> Step {
109    let key = format!(
110        "sofie.default {}.{} {owner:?}",
111        write.table.schema, write.table.name
112    );
113    Step::statement(
114        "SELECT pg_advisory_xact_lock(hashtextextended($1, 0))".to_owned(),
115        vec![SqlValue::Text(key)],
116        None,
117    )
118}
119
120pub trait Defaults: Aggregate {
121    const IS_DEFAULT: Column<Self, bool> = Column::new("is_default");
122
123    fn make_default(&mut self) {
124        *stored_mut::<Self>(self, "is_default") = SqlValue::Bool(true);
125    }
126
127    fn is_default(&self) -> bool {
128        stored::<Self>(self, "is_default") == Some(&SqlValue::Bool(true))
129    }
130}
131
132impl<A: Uses<Default>> Defaults for A {}