1use crate::error::{MongrelError, Result};
2use crate::memtable::Value;
3use crate::schema::ColumnDef;
4use serde::{Deserialize, Serialize};
5use sha2::{Digest, Sha256};
6use std::collections::HashSet;
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct TriggerEntry {
10 pub trigger: StoredTrigger,
11}
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct StoredTrigger {
15 pub name: String,
16 pub version: u64,
17 pub target: TriggerTarget,
18 pub timing: TriggerTiming,
19 pub event: TriggerEvent,
20 #[serde(default)]
21 pub update_of: Vec<String>,
22 #[serde(default)]
23 pub target_columns: Vec<ColumnDef>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub when: Option<TriggerExpr>,
26 pub program: TriggerProgram,
27 pub enabled: bool,
28 pub checksum: String,
29 pub created_epoch: u64,
30 pub updated_epoch: u64,
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub struct TriggerDefinition {
35 pub target: TriggerTarget,
36 pub timing: TriggerTiming,
37 pub event: TriggerEvent,
38 #[serde(default)]
39 pub update_of: Vec<String>,
40 #[serde(default)]
41 pub target_columns: Vec<ColumnDef>,
42 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub when: Option<TriggerExpr>,
44 pub program: TriggerProgram,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48pub struct TriggerConfig {
49 pub recursive_triggers: bool,
50 pub max_depth: u32,
51}
52
53impl Default for TriggerConfig {
54 fn default() -> Self {
55 Self {
56 recursive_triggers: false,
57 max_depth: 32,
58 }
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(rename_all = "snake_case", tag = "kind", content = "name")]
64pub enum TriggerTarget {
65 Table(String),
66 View(String),
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(rename_all = "snake_case")]
71pub enum TriggerTiming {
72 Before,
73 After,
74 InsteadOf,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
78#[serde(rename_all = "snake_case")]
79pub enum TriggerEvent {
80 Insert,
81 Update,
82 Delete,
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct TriggerProgram {
87 #[serde(default)]
88 pub steps: Vec<TriggerStep>,
89}
90
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
92#[serde(rename_all = "snake_case", tag = "kind")]
93pub enum TriggerStep {
94 SetNew {
95 cells: Vec<TriggerCell>,
96 },
97 Insert {
98 table: String,
99 cells: Vec<TriggerCell>,
100 },
101 UpdateByPk {
102 table: String,
103 pk: TriggerValue,
104 cells: Vec<TriggerCell>,
105 },
106 DeleteByPk {
107 table: String,
108 pk: TriggerValue,
109 },
110 Select {
111 id: String,
112 table: String,
113 #[serde(default)]
114 conditions: Vec<TriggerCondition>,
115 },
116 Raise {
117 action: TriggerRaiseAction,
118 message: TriggerValue,
119 },
120}
121
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123pub struct TriggerCell {
124 pub column_id: u16,
125 pub value: TriggerValue,
126}
127
128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
129#[serde(rename_all = "snake_case", tag = "kind")]
130pub enum TriggerCondition {
131 Pk { value: TriggerValue },
132 Eq { column_id: u16, value: TriggerValue },
133 IsNull { column_id: u16 },
134 IsNotNull { column_id: u16 },
135}
136
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138#[serde(rename_all = "snake_case", tag = "kind", content = "value")]
139pub enum TriggerValue {
140 Literal(Value),
141 NewColumn(u16),
142 OldColumn(u16),
143}
144
145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146#[serde(rename_all = "snake_case", tag = "kind")]
147pub enum TriggerExpr {
148 Value(TriggerValue),
149 Eq {
150 left: TriggerValue,
151 right: TriggerValue,
152 },
153 NotEq {
154 left: TriggerValue,
155 right: TriggerValue,
156 },
157 IsNull(TriggerValue),
158 IsNotNull(TriggerValue),
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
162#[serde(rename_all = "snake_case")]
163pub enum TriggerRaiseAction {
164 Abort,
165 Fail,
166 Rollback,
167 Ignore,
168}
169
170impl StoredTrigger {
171 pub fn new(name: impl Into<String>, definition: TriggerDefinition, epoch: u64) -> Result<Self> {
172 let name = name.into();
173 let checksum = checksum(ChecksumInput {
174 name: &name,
175 version: 1,
176 target: &definition.target,
177 timing: definition.timing,
178 event: definition.event,
179 update_of: &definition.update_of,
180 target_columns: &definition.target_columns,
181 when: &definition.when,
182 program: &definition.program,
183 })?;
184 let trigger = Self {
185 name,
186 version: 1,
187 target: definition.target,
188 timing: definition.timing,
189 event: definition.event,
190 update_of: definition.update_of,
191 target_columns: definition.target_columns,
192 when: definition.when,
193 program: definition.program,
194 enabled: true,
195 checksum,
196 created_epoch: epoch,
197 updated_epoch: epoch,
198 };
199 trigger.validate()?;
200 Ok(trigger)
201 }
202
203 pub fn replaced(&self, mut next: StoredTrigger, epoch: u64) -> Result<StoredTrigger> {
204 next.name = self.name.clone();
205 next.version = self.version + 1;
206 next.created_epoch = self.created_epoch;
207 next.updated_epoch = epoch;
208 next.checksum = checksum(ChecksumInput {
209 name: &next.name,
210 version: next.version,
211 target: &next.target,
212 timing: next.timing,
213 event: next.event,
214 update_of: &next.update_of,
215 target_columns: &next.target_columns,
216 when: &next.when,
217 program: &next.program,
218 })?;
219 next.validate()?;
220 Ok(next)
221 }
222
223 pub fn retarget_table(&self, new_name: impl Into<String>, epoch: u64) -> Result<Self> {
224 let mut next = self.clone();
225 next.version = next.version.saturating_add(1);
226 next.target = TriggerTarget::Table(new_name.into());
227 next.updated_epoch = epoch;
228 next.refresh_checksum()?;
229 Ok(next)
230 }
231
232 pub fn renamed_update_column(
233 &self,
234 old_name: &str,
235 new_name: impl Into<String>,
236 epoch: u64,
237 ) -> Result<Self> {
238 let new_name = new_name.into();
239 let mut next = self.clone();
240 let mut changed = false;
241 for column in &mut next.update_of {
242 if column == old_name {
243 *column = new_name.clone();
244 changed = true;
245 }
246 }
247 if changed {
248 next.version = next.version.saturating_add(1);
249 next.updated_epoch = epoch;
250 next.refresh_checksum()?;
251 }
252 Ok(next)
253 }
254
255 pub fn validate(&self) -> Result<()> {
256 validate_name("trigger", &self.name)?;
257 match &self.target {
258 TriggerTarget::Table(name) => validate_name("trigger table target", name)?,
259 TriggerTarget::View(name) => validate_name("trigger view target", name)?,
260 }
261 match (&self.target, self.timing) {
262 (TriggerTarget::Table(_), TriggerTiming::Before | TriggerTiming::After) => {
263 if !self.target_columns.is_empty() {
264 return Err(MongrelError::InvalidArgument(
265 "table triggers must not carry target_columns".into(),
266 ));
267 }
268 }
269 (TriggerTarget::View(_), TriggerTiming::InsteadOf) => {
270 validate_target_columns(&self.target_columns)?;
271 }
272 (TriggerTarget::Table(_), TriggerTiming::InsteadOf) => {
273 return Err(MongrelError::InvalidArgument(
274 "INSTEAD OF triggers target views, not tables".into(),
275 ));
276 }
277 (TriggerTarget::View(_), TriggerTiming::Before | TriggerTiming::After) => {
278 return Err(MongrelError::InvalidArgument(
279 "views support INSTEAD OF triggers, not BEFORE/AFTER triggers".into(),
280 ));
281 }
282 }
283 let mut update_cols = HashSet::new();
284 for col in &self.update_of {
285 validate_name("trigger UPDATE OF column", col)?;
286 if !update_cols.insert(col) {
287 return Err(MongrelError::InvalidArgument(format!(
288 "duplicate trigger UPDATE OF column {col:?}"
289 )));
290 }
291 }
292 for step in &self.program.steps {
293 step.validate()?;
294 }
295 Ok(())
296 }
297
298 fn refresh_checksum(&mut self) -> Result<()> {
299 self.checksum = checksum(ChecksumInput {
300 name: &self.name,
301 version: self.version,
302 target: &self.target,
303 timing: self.timing,
304 event: self.event,
305 update_of: &self.update_of,
306 target_columns: &self.target_columns,
307 when: &self.when,
308 program: &self.program,
309 })?;
310 self.validate()
311 }
312}
313
314impl TriggerStep {
315 fn validate(&self) -> Result<()> {
316 match self {
317 TriggerStep::SetNew { cells } => {
318 let mut seen = HashSet::new();
319 for cell in cells {
320 if !seen.insert(cell.column_id) {
321 return Err(MongrelError::InvalidArgument(format!(
322 "duplicate trigger cell column id {}",
323 cell.column_id
324 )));
325 }
326 }
327 Ok(())
328 }
329 TriggerStep::Insert { table, cells } | TriggerStep::UpdateByPk { table, cells, .. } => {
330 validate_name("trigger step table", table)?;
331 let mut seen = HashSet::new();
332 for cell in cells {
333 if !seen.insert(cell.column_id) {
334 return Err(MongrelError::InvalidArgument(format!(
335 "duplicate trigger cell column id {}",
336 cell.column_id
337 )));
338 }
339 }
340 Ok(())
341 }
342 TriggerStep::DeleteByPk { table, .. } | TriggerStep::Select { table, .. } => {
343 validate_name("trigger step table", table)
344 }
345 TriggerStep::Raise { .. } => Ok(()),
346 }
347 }
348}
349
350impl From<StoredTrigger> for TriggerEntry {
351 fn from(trigger: StoredTrigger) -> Self {
352 Self { trigger }
353 }
354}
355
356#[derive(Serialize)]
357struct ChecksumInput<'a> {
358 name: &'a str,
359 version: u64,
360 target: &'a TriggerTarget,
361 timing: TriggerTiming,
362 event: TriggerEvent,
363 update_of: &'a [String],
364 target_columns: &'a [ColumnDef],
365 when: &'a Option<TriggerExpr>,
366 program: &'a TriggerProgram,
367}
368
369fn checksum(input: ChecksumInput<'_>) -> Result<String> {
370 let bytes = serde_json::to_vec(&input)
371 .map_err(|e| MongrelError::Other(format!("trigger checksum serialize: {e}")))?;
372 Ok(hex(&Sha256::digest(bytes)))
373}
374
375fn hex(bytes: &[u8]) -> String {
376 const HEX: &[u8; 16] = b"0123456789abcdef";
377 let mut out = String::with_capacity(bytes.len() * 2);
378 for b in bytes {
379 out.push(HEX[(b >> 4) as usize] as char);
380 out.push(HEX[(b & 0x0f) as usize] as char);
381 }
382 out
383}
384
385fn validate_target_columns(columns: &[ColumnDef]) -> Result<()> {
386 if columns.is_empty() {
387 return Err(MongrelError::InvalidArgument(
388 "view triggers require target_columns".into(),
389 ));
390 }
391 let mut names = HashSet::new();
392 let mut ids = HashSet::new();
393 for column in columns {
394 validate_name("trigger target column", &column.name)?;
395 if !names.insert(&column.name) {
396 return Err(MongrelError::InvalidArgument(format!(
397 "duplicate trigger target column {:?}",
398 column.name
399 )));
400 }
401 if !ids.insert(column.id) {
402 return Err(MongrelError::InvalidArgument(format!(
403 "duplicate trigger target column id {}",
404 column.id
405 )));
406 }
407 }
408 Ok(())
409}
410
411fn validate_name(kind: &str, name: &str) -> Result<()> {
412 if name.is_empty()
413 || !name
414 .chars()
415 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
416 {
417 return Err(MongrelError::InvalidArgument(format!(
418 "{kind} name {name:?} must be non-empty ASCII alphanumeric, '_' or '-'"
419 )));
420 }
421 Ok(())
422}