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
49pub 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
106fn 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 {}