1use sofie_core::{Cast, Instant, Result, SqlValue, Uuid};
2
3use crate::aggregate::{Snapshot, Table, table};
4use crate::{Aggregate, Column, Field, Plan, Stamp};
5
6pub trait Behavior: Sync {
8 fn columns(&self) -> &'static [Field];
10
11 fn written(&self, write: &Write<'_>, values: &mut [SqlValue]);
12
13 fn guards(&self) -> bool {
14 false
15 }
16
17 fn hides(&self) -> Option<&'static str> {
18 None
19 }
20
21 fn broadcasts(&self) -> bool {
22 false
23 }
24
25 fn searched(&self) -> bool {
26 false
27 }
28
29 #[cfg(feature = "search")]
30 fn search_fields(&self) -> &'static [crate::SearchedField] {
31 &[]
32 }
33
34 fn planned(&self, write: &Write<'_>, plan: &mut Plan<'_>) -> Result<()> {
36 let _ = (write, plan);
37 Ok(())
38 }
39
40 #[cfg(feature = "retention")]
42 fn pruned(&self, schema: &str, name: &str, ids: &[Uuid], plan: &mut Plan<'_>) {
43 let _ = (schema, name, ids, plan);
44 }
45
46 fn name(&self) -> &'static str {
48 std::any::type_name::<Self>()
49 }
50}
51
52pub struct Write<'a> {
53 pub id: Uuid,
54 pub owner: Option<Uuid>,
55 pub stamp: Stamp,
56 pub(crate) table: &'a Table,
57 pub(crate) loaded: Option<&'a Snapshot>,
58 pub(crate) fields: &'a [SqlValue],
59 pub(crate) stored: &'a [SqlValue],
60}
61
62impl Write<'_> {
63 #[must_use]
64 pub fn loaded(&self, column: &str) -> Option<&SqlValue> {
65 let loaded = self.loaded?;
66 self.lookup(column, &loaded.stored, &loaded.fields)
67 }
68
69 #[must_use]
70 pub fn value(&self, column: &str) -> Option<&SqlValue> {
71 self.lookup(column, self.stored, self.fields)
72 }
73
74 #[cfg(any(feature = "codes", feature = "history"))]
75 pub(crate) fn deleted(&self) -> (bool, bool) {
76 let deleted =
77 |value: Option<&SqlValue>| value.is_some_and(|value| *value != SqlValue::Null);
78 (
79 deleted(self.loaded("deleted_at")),
80 deleted(self.value("deleted_at")),
81 )
82 }
83
84 fn lookup<'v>(
85 &self,
86 column: &str,
87 stored: &'v [SqlValue],
88 fields: &'v [SqlValue],
89 ) -> Option<&'v SqlValue> {
90 let position = |columns: &[String]| columns.iter().position(|name| name == column);
91 match position(&self.table.stored) {
92 Some(index) => stored.get(index),
93 None => fields.get(position(&self.table.columns)?),
94 }
95 }
96}
97
98pub trait Uses<B: Behavior>: Aggregate {}
99
100pub struct Versioned;
102
103impl Behavior for Versioned {
104 fn columns(&self) -> &'static [Field] {
105 const { &[Field::new("version", i64::COLUMNS, false)] }
106 }
107
108 fn written(&self, write: &Write<'_>, values: &mut [SqlValue]) {
109 values[0] = SqlValue::Int(version(write.loaded("version")) + 1);
110 }
111
112 fn guards(&self) -> bool {
113 true
114 }
115}
116
117pub(crate) fn version(stored: Option<&SqlValue>) -> i64 {
118 match stored {
119 Some(SqlValue::Int(version)) => *version,
120 _ => 0,
121 }
122}
123
124pub struct Timestamps;
125
126impl Behavior for Timestamps {
127 fn columns(&self) -> &'static [Field] {
128 const {
129 &[
130 Field::new("created_at", Instant::COLUMNS, false),
131 Field::new("updated_at", Instant::COLUMNS, false),
132 ]
133 }
134 }
135
136 fn written(&self, write: &Write<'_>, values: &mut [SqlValue]) {
137 let now = SqlValue::Timestamp(write.stamp.now.timestamp());
138 values[0] = write.loaded("created_at").unwrap_or(&now).clone();
139 values[1] = now;
140 }
141}
142
143pub trait Timestamped: Aggregate {
144 const CREATED_AT: Column<Self, Instant> = Column::new("created_at");
145 const UPDATED_AT: Column<Self, Instant> = Column::new("updated_at");
146
147 fn created_at(&self) -> Option<Instant> {
148 stored_time::<Self>(self, "created_at")
149 }
150
151 fn updated_at(&self) -> Option<Instant> {
152 stored_time::<Self>(self, "updated_at")
153 }
154}
155
156impl<A: Uses<Timestamps>> Timestamped for A {}
157
158pub(crate) fn stored<'a, A: Aggregate>(aggregate: &'a A, column: &str) -> Option<&'a SqlValue> {
159 aggregate
160 .meta()
161 .stored
162 .get(table::<A>().stored_index(column))
163}
164
165pub(crate) fn stored_time<A: Aggregate>(aggregate: &A, column: &str) -> Option<Instant> {
166 match stored(aggregate, column) {
167 Some(SqlValue::Timestamp(time)) => Some(Instant::from(*time)),
168 _ => None,
169 }
170}