Skip to main content

mongreldb_core/
trigger.rs

1use crate::error::{MongrelError, Result};
2use crate::memtable::Value;
3use crate::schema::ColumnDef;
4use serde::de::{self, Deserializer};
5use serde::ser::{SerializeMap, Serializer};
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8use std::collections::HashSet;
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct TriggerEntry {
12    pub trigger: StoredTrigger,
13}
14
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct StoredTrigger {
17    pub name: String,
18    pub version: u64,
19    pub target: TriggerTarget,
20    pub timing: TriggerTiming,
21    pub event: TriggerEvent,
22    #[serde(default)]
23    pub update_of: Vec<String>,
24    #[serde(default)]
25    pub target_columns: Vec<ColumnDef>,
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub when: Option<TriggerExpr>,
28    pub program: TriggerProgram,
29    pub enabled: bool,
30    pub checksum: String,
31    pub created_epoch: u64,
32    pub updated_epoch: u64,
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub struct TriggerDefinition {
37    pub target: TriggerTarget,
38    pub timing: TriggerTiming,
39    pub event: TriggerEvent,
40    #[serde(default)]
41    pub update_of: Vec<String>,
42    #[serde(default)]
43    pub target_columns: Vec<ColumnDef>,
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub when: Option<TriggerExpr>,
46    pub program: TriggerProgram,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50pub struct TriggerConfig {
51    pub recursive_triggers: bool,
52    pub max_depth: u32,
53    #[serde(default)]
54    pub max_loop_iterations: u32,
55}
56
57impl Default for TriggerConfig {
58    fn default() -> Self {
59        Self {
60            recursive_triggers: false,
61            max_depth: 32,
62            max_loop_iterations: 10_000,
63        }
64    }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68#[serde(rename_all = "snake_case", tag = "kind", content = "name")]
69pub enum TriggerTarget {
70    Table(String),
71    View(String),
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
75#[serde(rename_all = "snake_case")]
76pub enum TriggerTiming {
77    Before,
78    After,
79    InsteadOf,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
83#[serde(rename_all = "snake_case")]
84pub enum TriggerEvent {
85    Insert,
86    Update,
87    Delete,
88}
89
90#[derive(Debug, Clone, PartialEq)]
91pub struct TriggerProgram {
92    pub steps: Vec<TriggerStep>,
93}
94
95#[derive(Debug, Clone, PartialEq)]
96pub enum TriggerStep {
97    SetNew {
98        cells: Vec<TriggerCell>,
99    },
100    Insert {
101        table: String,
102        cells: Vec<TriggerCell>,
103    },
104    UpdateByPk {
105        table: String,
106        pk: TriggerValue,
107        cells: Vec<TriggerCell>,
108    },
109    DeleteByPk {
110        table: String,
111        pk: TriggerValue,
112    },
113    Select {
114        id: String,
115        table: String,
116        conditions: Vec<TriggerCondition>,
117    },
118    Foreach {
119        id: String,
120        steps: Vec<TriggerStep>,
121    },
122    DeleteWhere {
123        table: String,
124        conditions: Vec<TriggerCondition>,
125    },
126    UpdateWhere {
127        table: String,
128        conditions: Vec<TriggerCondition>,
129        cells: Vec<TriggerCell>,
130    },
131    Raise {
132        action: TriggerRaiseAction,
133        message: TriggerValue,
134    },
135}
136
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138pub struct TriggerCell {
139    pub column_id: u16,
140    pub value: TriggerValue,
141}
142
143#[derive(Debug, Clone, PartialEq)]
144pub enum TriggerCondition {
145    Pk {
146        value: TriggerValue,
147    },
148    Eq {
149        column_id: u16,
150        value: TriggerValue,
151    },
152    NotEq {
153        column_id: u16,
154        value: TriggerValue,
155    },
156    Lt {
157        column_id: u16,
158        value: TriggerValue,
159    },
160    Lte {
161        column_id: u16,
162        value: TriggerValue,
163    },
164    Gt {
165        column_id: u16,
166        value: TriggerValue,
167    },
168    Gte {
169        column_id: u16,
170        value: TriggerValue,
171    },
172    IsNull {
173        column_id: u16,
174    },
175    IsNotNull {
176        column_id: u16,
177    },
178    And {
179        left: Box<TriggerCondition>,
180        right: Box<TriggerCondition>,
181    },
182    Or {
183        left: Box<TriggerCondition>,
184        right: Box<TriggerCondition>,
185    },
186    Not(Box<TriggerCondition>),
187}
188
189#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
190#[serde(rename_all = "snake_case", tag = "kind", content = "value")]
191pub enum TriggerValue {
192    Literal(Value),
193    NewColumn(u16),
194    OldColumn(u16),
195    SelectedColumn(u16),
196}
197
198#[derive(Debug, Clone, PartialEq)]
199pub enum TriggerExpr {
200    Value(TriggerValue),
201    Eq {
202        left: TriggerValue,
203        right: TriggerValue,
204    },
205    NotEq {
206        left: TriggerValue,
207        right: TriggerValue,
208    },
209    Lt {
210        left: TriggerValue,
211        right: TriggerValue,
212    },
213    Lte {
214        left: TriggerValue,
215        right: TriggerValue,
216    },
217    Gt {
218        left: TriggerValue,
219        right: TriggerValue,
220    },
221    Gte {
222        left: TriggerValue,
223        right: TriggerValue,
224    },
225    IsNull(TriggerValue),
226    IsNotNull(TriggerValue),
227    And {
228        left: Box<TriggerExpr>,
229        right: Box<TriggerExpr>,
230    },
231    Or {
232        left: Box<TriggerExpr>,
233        right: Box<TriggerExpr>,
234    },
235    Not(Box<TriggerExpr>),
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
239#[serde(rename_all = "snake_case")]
240pub enum TriggerRaiseAction {
241    Abort,
242    Fail,
243    Rollback,
244    Ignore,
245}
246
247impl Serialize for TriggerProgram {
248    fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
249        let mut map = serializer.serialize_map(Some(1))?;
250        map.serialize_entry("steps", &self.steps)?;
251        map.end()
252    }
253}
254
255impl<'de> Deserialize<'de> for TriggerProgram {
256    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
257        let value = serde_json::Value::deserialize(deserializer)?;
258        let obj = value
259            .as_object()
260            .ok_or_else(|| de::Error::custom("expected object"))?;
261        let steps = match obj.get("steps") {
262            Some(v) => parse_trigger_steps(v).map_err(de::Error::custom)?,
263            None => Vec::new(),
264        };
265        Ok(TriggerProgram { steps })
266    }
267}
268
269impl Serialize for TriggerStep {
270    fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
271        let mut map = serializer.serialize_map(None)?;
272        match self {
273            TriggerStep::SetNew { cells } => {
274                map.serialize_entry("kind", "set_new")?;
275                map.serialize_entry("cells", cells)?;
276            }
277            TriggerStep::Insert { table, cells } => {
278                map.serialize_entry("kind", "insert")?;
279                map.serialize_entry("table", table)?;
280                map.serialize_entry("cells", cells)?;
281            }
282            TriggerStep::UpdateByPk { table, pk, cells } => {
283                map.serialize_entry("kind", "update_by_pk")?;
284                map.serialize_entry("table", table)?;
285                map.serialize_entry("pk", pk)?;
286                map.serialize_entry("cells", cells)?;
287            }
288            TriggerStep::DeleteByPk { table, pk } => {
289                map.serialize_entry("kind", "delete_by_pk")?;
290                map.serialize_entry("table", table)?;
291                map.serialize_entry("pk", pk)?;
292            }
293            TriggerStep::Select {
294                id,
295                table,
296                conditions,
297            } => {
298                map.serialize_entry("kind", "select")?;
299                map.serialize_entry("id", id)?;
300                map.serialize_entry("table", table)?;
301                map.serialize_entry("conditions", conditions)?;
302            }
303            TriggerStep::Foreach { id, steps } => {
304                map.serialize_entry("kind", "foreach")?;
305                map.serialize_entry("id", id)?;
306                map.serialize_entry("steps", steps)?;
307            }
308            TriggerStep::DeleteWhere { table, conditions } => {
309                map.serialize_entry("kind", "delete_where")?;
310                map.serialize_entry("table", table)?;
311                map.serialize_entry("conditions", conditions)?;
312            }
313            TriggerStep::UpdateWhere {
314                table,
315                conditions,
316                cells,
317            } => {
318                map.serialize_entry("kind", "update_where")?;
319                map.serialize_entry("table", table)?;
320                map.serialize_entry("conditions", conditions)?;
321                map.serialize_entry("cells", cells)?;
322            }
323            TriggerStep::Raise { action, message } => {
324                map.serialize_entry("kind", "raise")?;
325                map.serialize_entry("action", action)?;
326                map.serialize_entry("message", message)?;
327            }
328        }
329        map.end()
330    }
331}
332
333impl<'de> Deserialize<'de> for TriggerStep {
334    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
335        let value = serde_json::Value::deserialize(deserializer)?;
336        parse_trigger_step(&value).map_err(de::Error::custom)
337    }
338}
339
340impl Serialize for TriggerCondition {
341    fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
342        let mut map = serializer.serialize_map(None)?;
343        match self {
344            TriggerCondition::Pk { value } => {
345                map.serialize_entry("kind", "pk")?;
346                map.serialize_entry("value", value)?;
347            }
348            TriggerCondition::Eq { column_id, value } => {
349                map.serialize_entry("kind", "eq")?;
350                map.serialize_entry("column_id", column_id)?;
351                map.serialize_entry("value", value)?;
352            }
353            TriggerCondition::NotEq { column_id, value } => {
354                map.serialize_entry("kind", "not_eq")?;
355                map.serialize_entry("column_id", column_id)?;
356                map.serialize_entry("value", value)?;
357            }
358            TriggerCondition::Lt { column_id, value } => {
359                map.serialize_entry("kind", "lt")?;
360                map.serialize_entry("column_id", column_id)?;
361                map.serialize_entry("value", value)?;
362            }
363            TriggerCondition::Lte { column_id, value } => {
364                map.serialize_entry("kind", "lte")?;
365                map.serialize_entry("column_id", column_id)?;
366                map.serialize_entry("value", value)?;
367            }
368            TriggerCondition::Gt { column_id, value } => {
369                map.serialize_entry("kind", "gt")?;
370                map.serialize_entry("column_id", column_id)?;
371                map.serialize_entry("value", value)?;
372            }
373            TriggerCondition::Gte { column_id, value } => {
374                map.serialize_entry("kind", "gte")?;
375                map.serialize_entry("column_id", column_id)?;
376                map.serialize_entry("value", value)?;
377            }
378            TriggerCondition::IsNull { column_id } => {
379                map.serialize_entry("kind", "is_null")?;
380                map.serialize_entry("column_id", column_id)?;
381            }
382            TriggerCondition::IsNotNull { column_id } => {
383                map.serialize_entry("kind", "is_not_null")?;
384                map.serialize_entry("column_id", column_id)?;
385            }
386            TriggerCondition::And { left, right } => {
387                map.serialize_entry("kind", "and")?;
388                map.serialize_entry("left", left)?;
389                map.serialize_entry("right", right)?;
390            }
391            TriggerCondition::Or { left, right } => {
392                map.serialize_entry("kind", "or")?;
393                map.serialize_entry("left", left)?;
394                map.serialize_entry("right", right)?;
395            }
396            TriggerCondition::Not(value) => {
397                map.serialize_entry("kind", "not")?;
398                map.serialize_entry("value", value)?;
399            }
400        }
401        map.end()
402    }
403}
404
405impl<'de> Deserialize<'de> for TriggerCondition {
406    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
407        let value = serde_json::Value::deserialize(deserializer)?;
408        parse_trigger_condition(&value).map_err(de::Error::custom)
409    }
410}
411
412impl Serialize for TriggerExpr {
413    fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
414        let mut map = serializer.serialize_map(None)?;
415        match self {
416            TriggerExpr::Value(value) => {
417                map.serialize_entry("kind", "value")?;
418                map.serialize_entry("value", value)?;
419            }
420            TriggerExpr::Eq { left, right } => {
421                map.serialize_entry("kind", "eq")?;
422                map.serialize_entry("left", left)?;
423                map.serialize_entry("right", right)?;
424            }
425            TriggerExpr::NotEq { left, right } => {
426                map.serialize_entry("kind", "not_eq")?;
427                map.serialize_entry("left", left)?;
428                map.serialize_entry("right", right)?;
429            }
430            TriggerExpr::Lt { left, right } => {
431                map.serialize_entry("kind", "lt")?;
432                map.serialize_entry("left", left)?;
433                map.serialize_entry("right", right)?;
434            }
435            TriggerExpr::Lte { left, right } => {
436                map.serialize_entry("kind", "lte")?;
437                map.serialize_entry("left", left)?;
438                map.serialize_entry("right", right)?;
439            }
440            TriggerExpr::Gt { left, right } => {
441                map.serialize_entry("kind", "gt")?;
442                map.serialize_entry("left", left)?;
443                map.serialize_entry("right", right)?;
444            }
445            TriggerExpr::Gte { left, right } => {
446                map.serialize_entry("kind", "gte")?;
447                map.serialize_entry("left", left)?;
448                map.serialize_entry("right", right)?;
449            }
450            TriggerExpr::IsNull(value) => {
451                map.serialize_entry("kind", "is_null")?;
452                map.serialize_entry("value", value)?;
453            }
454            TriggerExpr::IsNotNull(value) => {
455                map.serialize_entry("kind", "is_not_null")?;
456                map.serialize_entry("value", value)?;
457            }
458            TriggerExpr::And { left, right } => {
459                map.serialize_entry("kind", "and")?;
460                map.serialize_entry("left", left)?;
461                map.serialize_entry("right", right)?;
462            }
463            TriggerExpr::Or { left, right } => {
464                map.serialize_entry("kind", "or")?;
465                map.serialize_entry("left", left)?;
466                map.serialize_entry("right", right)?;
467            }
468            TriggerExpr::Not(value) => {
469                map.serialize_entry("kind", "not")?;
470                map.serialize_entry("value", value)?;
471            }
472        }
473        map.end()
474    }
475}
476
477impl<'de> Deserialize<'de> for TriggerExpr {
478    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
479        let value = serde_json::Value::deserialize(deserializer)?;
480        parse_trigger_expr(&value).map_err(de::Error::custom)
481    }
482}
483
484fn parse_trigger_steps(value: &serde_json::Value) -> std::result::Result<Vec<TriggerStep>, String> {
485    value
486        .as_array()
487        .ok_or_else(|| "expected array for steps".to_string())?
488        .iter()
489        .map(parse_trigger_step)
490        .collect()
491}
492
493fn parse_trigger_step(value: &serde_json::Value) -> std::result::Result<TriggerStep, String> {
494    let obj = value
495        .as_object()
496        .ok_or_else(|| "expected object".to_string())?;
497    let kind = obj
498        .get("kind")
499        .and_then(|v| v.as_str())
500        .ok_or_else(|| "missing kind".to_string())?;
501    match kind {
502        "set_new" => Ok(TriggerStep::SetNew {
503            cells: parse_trigger_cells(
504                obj.get("cells")
505                    .ok_or_else(|| "missing cells".to_string())?,
506            )?,
507        }),
508        "insert" => Ok(TriggerStep::Insert {
509            table: parse_string(
510                obj.get("table")
511                    .ok_or_else(|| "missing table".to_string())?,
512                "table",
513            )?,
514            cells: parse_trigger_cells(
515                obj.get("cells")
516                    .ok_or_else(|| "missing cells".to_string())?,
517            )?,
518        }),
519        "update_by_pk" => Ok(TriggerStep::UpdateByPk {
520            table: parse_string(
521                obj.get("table")
522                    .ok_or_else(|| "missing table".to_string())?,
523                "table",
524            )?,
525            pk: parse_trigger_value(obj.get("pk").ok_or_else(|| "missing pk".to_string())?)?,
526            cells: parse_trigger_cells(
527                obj.get("cells")
528                    .ok_or_else(|| "missing cells".to_string())?,
529            )?,
530        }),
531        "delete_by_pk" => Ok(TriggerStep::DeleteByPk {
532            table: parse_string(
533                obj.get("table")
534                    .ok_or_else(|| "missing table".to_string())?,
535                "table",
536            )?,
537            pk: parse_trigger_value(obj.get("pk").ok_or_else(|| "missing pk".to_string())?)?,
538        }),
539        "select" => Ok(TriggerStep::Select {
540            id: parse_string(obj.get("id").ok_or_else(|| "missing id".to_string())?, "id")?,
541            table: parse_string(
542                obj.get("table")
543                    .ok_or_else(|| "missing table".to_string())?,
544                "table",
545            )?,
546            conditions: parse_trigger_conditions_optional(obj)?,
547        }),
548        "foreach" => Ok(TriggerStep::Foreach {
549            id: parse_string(obj.get("id").ok_or_else(|| "missing id".to_string())?, "id")?,
550            steps: parse_trigger_steps(
551                obj.get("steps")
552                    .ok_or_else(|| "missing steps".to_string())?,
553            )?,
554        }),
555        "delete_where" => Ok(TriggerStep::DeleteWhere {
556            table: parse_string(
557                obj.get("table")
558                    .ok_or_else(|| "missing table".to_string())?,
559                "table",
560            )?,
561            conditions: parse_trigger_conditions_optional(obj)?,
562        }),
563        "update_where" => Ok(TriggerStep::UpdateWhere {
564            table: parse_string(
565                obj.get("table")
566                    .ok_or_else(|| "missing table".to_string())?,
567                "table",
568            )?,
569            conditions: parse_trigger_conditions_optional(obj)?,
570            cells: parse_trigger_cells(
571                obj.get("cells")
572                    .ok_or_else(|| "missing cells".to_string())?,
573            )?,
574        }),
575        "raise" => Ok(TriggerStep::Raise {
576            action: serde_json::from_value::<TriggerRaiseAction>(
577                obj.get("action")
578                    .ok_or_else(|| "missing action".to_string())?
579                    .clone(),
580            )
581            .map_err(|e| e.to_string())?,
582            message: parse_trigger_value(
583                obj.get("message")
584                    .ok_or_else(|| "missing message".to_string())?,
585            )?,
586        }),
587        _ => Err(format!("unknown TriggerStep kind: {kind}")),
588    }
589}
590
591fn parse_trigger_conditions_optional(
592    obj: &serde_json::Map<String, serde_json::Value>,
593) -> std::result::Result<Vec<TriggerCondition>, String> {
594    match obj.get("conditions") {
595        Some(v) => parse_trigger_conditions(v),
596        None => Ok(Vec::new()),
597    }
598}
599
600fn parse_trigger_conditions(
601    value: &serde_json::Value,
602) -> std::result::Result<Vec<TriggerCondition>, String> {
603    value
604        .as_array()
605        .ok_or_else(|| "expected array for conditions".to_string())?
606        .iter()
607        .map(parse_trigger_condition)
608        .collect()
609}
610
611fn parse_trigger_condition(
612    value: &serde_json::Value,
613) -> std::result::Result<TriggerCondition, String> {
614    let obj = value
615        .as_object()
616        .ok_or_else(|| "expected object".to_string())?;
617    let kind = obj
618        .get("kind")
619        .and_then(|v| v.as_str())
620        .ok_or_else(|| "missing kind".to_string())?;
621    match kind {
622        "pk" => Ok(TriggerCondition::Pk {
623            value: parse_trigger_value(
624                obj.get("value")
625                    .ok_or_else(|| "missing value".to_string())?,
626            )?,
627        }),
628        "eq" | "not_eq" | "lt" | "lte" | "gt" | "gte" => parse_trigger_condition_binary(obj, kind),
629        "is_null" => Ok(TriggerCondition::IsNull {
630            column_id: parse_u16(
631                obj.get("column_id")
632                    .ok_or_else(|| "missing column_id".to_string())?,
633                "column_id",
634            )?,
635        }),
636        "is_not_null" => Ok(TriggerCondition::IsNotNull {
637            column_id: parse_u16(
638                obj.get("column_id")
639                    .ok_or_else(|| "missing column_id".to_string())?,
640                "column_id",
641            )?,
642        }),
643        "and" => Ok(TriggerCondition::And {
644            left: Box::new(parse_trigger_condition(
645                obj.get("left").ok_or_else(|| "missing left".to_string())?,
646            )?),
647            right: Box::new(parse_trigger_condition(
648                obj.get("right")
649                    .ok_or_else(|| "missing right".to_string())?,
650            )?),
651        }),
652        "or" => Ok(TriggerCondition::Or {
653            left: Box::new(parse_trigger_condition(
654                obj.get("left").ok_or_else(|| "missing left".to_string())?,
655            )?),
656            right: Box::new(parse_trigger_condition(
657                obj.get("right")
658                    .ok_or_else(|| "missing right".to_string())?,
659            )?),
660        }),
661        "not" => Ok(TriggerCondition::Not(Box::new(parse_trigger_condition(
662            obj.get("value")
663                .ok_or_else(|| "missing value".to_string())?,
664        )?))),
665        _ => Err(format!("unknown TriggerCondition kind: {kind}")),
666    }
667}
668
669fn parse_trigger_condition_binary(
670    obj: &serde_json::Map<String, serde_json::Value>,
671    kind: &str,
672) -> std::result::Result<TriggerCondition, String> {
673    let column_id = parse_u16(
674        obj.get("column_id")
675            .ok_or_else(|| "missing column_id".to_string())?,
676        "column_id",
677    )?;
678    let value = parse_trigger_value(
679        obj.get("value")
680            .ok_or_else(|| "missing value".to_string())?,
681    )?;
682    Ok(match kind {
683        "eq" => TriggerCondition::Eq { column_id, value },
684        "not_eq" => TriggerCondition::NotEq { column_id, value },
685        "lt" => TriggerCondition::Lt { column_id, value },
686        "lte" => TriggerCondition::Lte { column_id, value },
687        "gt" => TriggerCondition::Gt { column_id, value },
688        "gte" => TriggerCondition::Gte { column_id, value },
689        _ => return Err(format!("unexpected binary condition kind: {kind}")),
690    })
691}
692
693fn parse_trigger_expr(value: &serde_json::Value) -> std::result::Result<TriggerExpr, String> {
694    let obj = value
695        .as_object()
696        .ok_or_else(|| "expected object".to_string())?;
697    let kind = obj
698        .get("kind")
699        .and_then(|v| v.as_str())
700        .ok_or_else(|| "missing kind".to_string())?;
701    match kind {
702        "value" => Ok(TriggerExpr::Value(parse_trigger_value(
703            obj.get("value")
704                .ok_or_else(|| "missing value".to_string())?,
705        )?)),
706        "eq" | "not_eq" | "lt" | "lte" | "gt" | "gte" => parse_trigger_expr_binary(obj, kind),
707        "is_null" => Ok(TriggerExpr::IsNull(parse_trigger_value(
708            obj.get("value")
709                .ok_or_else(|| "missing value".to_string())?,
710        )?)),
711        "is_not_null" => Ok(TriggerExpr::IsNotNull(parse_trigger_value(
712            obj.get("value")
713                .ok_or_else(|| "missing value".to_string())?,
714        )?)),
715        "and" => Ok(TriggerExpr::And {
716            left: Box::new(parse_trigger_expr(
717                obj.get("left").ok_or_else(|| "missing left".to_string())?,
718            )?),
719            right: Box::new(parse_trigger_expr(
720                obj.get("right")
721                    .ok_or_else(|| "missing right".to_string())?,
722            )?),
723        }),
724        "or" => Ok(TriggerExpr::Or {
725            left: Box::new(parse_trigger_expr(
726                obj.get("left").ok_or_else(|| "missing left".to_string())?,
727            )?),
728            right: Box::new(parse_trigger_expr(
729                obj.get("right")
730                    .ok_or_else(|| "missing right".to_string())?,
731            )?),
732        }),
733        "not" => Ok(TriggerExpr::Not(Box::new(parse_trigger_expr(
734            obj.get("value")
735                .ok_or_else(|| "missing value".to_string())?,
736        )?))),
737        _ => Err(format!("unknown TriggerExpr kind: {kind}")),
738    }
739}
740
741fn parse_trigger_expr_binary(
742    obj: &serde_json::Map<String, serde_json::Value>,
743    kind: &str,
744) -> std::result::Result<TriggerExpr, String> {
745    let left = parse_trigger_value(obj.get("left").ok_or_else(|| "missing left".to_string())?)?;
746    let right = parse_trigger_value(
747        obj.get("right")
748            .ok_or_else(|| "missing right".to_string())?,
749    )?;
750    Ok(match kind {
751        "eq" => TriggerExpr::Eq { left, right },
752        "not_eq" => TriggerExpr::NotEq { left, right },
753        "lt" => TriggerExpr::Lt { left, right },
754        "lte" => TriggerExpr::Lte { left, right },
755        "gt" => TriggerExpr::Gt { left, right },
756        "gte" => TriggerExpr::Gte { left, right },
757        _ => return Err(format!("unexpected binary expr kind: {kind}")),
758    })
759}
760
761fn parse_trigger_value(value: &serde_json::Value) -> std::result::Result<TriggerValue, String> {
762    serde_json::from_value(value.clone()).map_err(|e| e.to_string())
763}
764
765fn parse_trigger_cells(value: &serde_json::Value) -> std::result::Result<Vec<TriggerCell>, String> {
766    value
767        .as_array()
768        .ok_or_else(|| "expected array for cells".to_string())?
769        .iter()
770        .map(parse_trigger_cell)
771        .collect()
772}
773
774fn parse_trigger_cell(value: &serde_json::Value) -> std::result::Result<TriggerCell, String> {
775    serde_json::from_value(value.clone()).map_err(|e| e.to_string())
776}
777
778fn parse_u16(value: &serde_json::Value, field: &str) -> std::result::Result<u16, String> {
779    value
780        .as_u64()
781        .and_then(|n| u16::try_from(n).ok())
782        .ok_or_else(|| format!("expected u16 for {field}"))
783}
784
785fn parse_string(value: &serde_json::Value, field: &str) -> std::result::Result<String, String> {
786    value
787        .as_str()
788        .map(|s| s.to_string())
789        .ok_or_else(|| format!("expected string for {field}"))
790}
791
792impl StoredTrigger {
793    pub fn new(name: impl Into<String>, definition: TriggerDefinition, epoch: u64) -> Result<Self> {
794        let name = name.into();
795        let checksum = checksum(ChecksumInput {
796            name: &name,
797            version: 1,
798            target: &definition.target,
799            timing: definition.timing,
800            event: definition.event,
801            update_of: &definition.update_of,
802            target_columns: &definition.target_columns,
803            when: &definition.when,
804            program: &definition.program,
805        })?;
806        let trigger = Self {
807            name,
808            version: 1,
809            target: definition.target,
810            timing: definition.timing,
811            event: definition.event,
812            update_of: definition.update_of,
813            target_columns: definition.target_columns,
814            when: definition.when,
815            program: definition.program,
816            enabled: true,
817            checksum,
818            created_epoch: epoch,
819            updated_epoch: epoch,
820        };
821        trigger.validate()?;
822        Ok(trigger)
823    }
824
825    pub fn replaced(&self, mut next: StoredTrigger, epoch: u64) -> Result<StoredTrigger> {
826        next.name = self.name.clone();
827        next.version = self.version + 1;
828        next.created_epoch = self.created_epoch;
829        next.updated_epoch = epoch;
830        next.checksum = checksum(ChecksumInput {
831            name: &next.name,
832            version: next.version,
833            target: &next.target,
834            timing: next.timing,
835            event: next.event,
836            update_of: &next.update_of,
837            target_columns: &next.target_columns,
838            when: &next.when,
839            program: &next.program,
840        })?;
841        next.validate()?;
842        Ok(next)
843    }
844
845    pub fn retarget_table(&self, new_name: impl Into<String>, epoch: u64) -> Result<Self> {
846        let mut next = self.clone();
847        next.version = next.version.saturating_add(1);
848        next.target = TriggerTarget::Table(new_name.into());
849        next.updated_epoch = epoch;
850        next.refresh_checksum()?;
851        Ok(next)
852    }
853
854    pub fn renamed_update_column(
855        &self,
856        old_name: &str,
857        new_name: impl Into<String>,
858        epoch: u64,
859    ) -> Result<Self> {
860        let new_name = new_name.into();
861        let mut next = self.clone();
862        let mut changed = false;
863        for column in &mut next.update_of {
864            if column == old_name {
865                *column = new_name.clone();
866                changed = true;
867            }
868        }
869        if changed {
870            next.version = next.version.saturating_add(1);
871            next.updated_epoch = epoch;
872            next.refresh_checksum()?;
873        }
874        Ok(next)
875    }
876
877    pub fn validate(&self) -> Result<()> {
878        validate_name("trigger", &self.name)?;
879        match &self.target {
880            TriggerTarget::Table(name) => validate_name("trigger table target", name)?,
881            TriggerTarget::View(name) => validate_name("trigger view target", name)?,
882        }
883        match (&self.target, self.timing) {
884            (TriggerTarget::Table(_), TriggerTiming::Before | TriggerTiming::After) => {
885                if !self.target_columns.is_empty() {
886                    return Err(MongrelError::InvalidArgument(
887                        "table triggers must not carry target_columns".into(),
888                    ));
889                }
890            }
891            (TriggerTarget::View(_), TriggerTiming::InsteadOf) => {
892                validate_target_columns(&self.target_columns)?;
893            }
894            (TriggerTarget::Table(_), TriggerTiming::InsteadOf) => {
895                return Err(MongrelError::InvalidArgument(
896                    "INSTEAD OF triggers target views, not tables".into(),
897                ));
898            }
899            (TriggerTarget::View(_), TriggerTiming::Before | TriggerTiming::After) => {
900                return Err(MongrelError::InvalidArgument(
901                    "views support INSTEAD OF triggers, not BEFORE/AFTER triggers".into(),
902                ));
903            }
904        }
905        let mut update_cols = HashSet::new();
906        for col in &self.update_of {
907            validate_name("trigger UPDATE OF column", col)?;
908            if !update_cols.insert(col) {
909                return Err(MongrelError::InvalidArgument(format!(
910                    "duplicate trigger UPDATE OF column {col:?}"
911                )));
912            }
913        }
914        for step in &self.program.steps {
915            step.validate()?;
916        }
917        Ok(())
918    }
919
920    fn refresh_checksum(&mut self) -> Result<()> {
921        self.checksum = checksum(ChecksumInput {
922            name: &self.name,
923            version: self.version,
924            target: &self.target,
925            timing: self.timing,
926            event: self.event,
927            update_of: &self.update_of,
928            target_columns: &self.target_columns,
929            when: &self.when,
930            program: &self.program,
931        })?;
932        self.validate()
933    }
934}
935
936impl TriggerStep {
937    fn validate(&self) -> Result<()> {
938        match self {
939            TriggerStep::SetNew { cells } => {
940                let mut seen = HashSet::new();
941                for cell in cells {
942                    if !seen.insert(cell.column_id) {
943                        return Err(MongrelError::InvalidArgument(format!(
944                            "duplicate trigger cell column id {}",
945                            cell.column_id
946                        )));
947                    }
948                }
949                Ok(())
950            }
951            TriggerStep::Insert { table, cells } | TriggerStep::UpdateByPk { table, cells, .. } => {
952                validate_name("trigger step table", table)?;
953                let mut seen = HashSet::new();
954                for cell in cells {
955                    if !seen.insert(cell.column_id) {
956                        return Err(MongrelError::InvalidArgument(format!(
957                            "duplicate trigger cell column id {}",
958                            cell.column_id
959                        )));
960                    }
961                }
962                Ok(())
963            }
964            TriggerStep::DeleteByPk { table, .. } | TriggerStep::Select { table, .. } => {
965                validate_name("trigger step table", table)
966            }
967            TriggerStep::DeleteWhere { table, .. } => validate_name("trigger step table", table),
968            TriggerStep::UpdateWhere { table, cells, .. } => {
969                validate_name("trigger step table", table)?;
970                let mut seen = HashSet::new();
971                for cell in cells {
972                    if !seen.insert(cell.column_id) {
973                        return Err(MongrelError::InvalidArgument(format!(
974                            "duplicate trigger cell column id {}",
975                            cell.column_id
976                        )));
977                    }
978                }
979                Ok(())
980            }
981            TriggerStep::Foreach { id, .. } => {
982                if id.is_empty() {
983                    return Err(MongrelError::InvalidArgument(
984                        "foreach id must not be empty".into(),
985                    ));
986                }
987                Ok(())
988            }
989            TriggerStep::Raise { .. } => Ok(()),
990        }
991    }
992}
993
994impl From<StoredTrigger> for TriggerEntry {
995    fn from(trigger: StoredTrigger) -> Self {
996        Self { trigger }
997    }
998}
999
1000#[derive(Serialize)]
1001struct ChecksumInput<'a> {
1002    name: &'a str,
1003    version: u64,
1004    target: &'a TriggerTarget,
1005    timing: TriggerTiming,
1006    event: TriggerEvent,
1007    update_of: &'a [String],
1008    target_columns: &'a [ColumnDef],
1009    when: &'a Option<TriggerExpr>,
1010    program: &'a TriggerProgram,
1011}
1012
1013fn checksum(input: ChecksumInput<'_>) -> Result<String> {
1014    let bytes = serde_json::to_vec(&input)
1015        .map_err(|e| MongrelError::Other(format!("trigger checksum serialize: {e}")))?;
1016    Ok(hex(&Sha256::digest(bytes)))
1017}
1018
1019fn hex(bytes: &[u8]) -> String {
1020    const HEX: &[u8; 16] = b"0123456789abcdef";
1021    let mut out = String::with_capacity(bytes.len() * 2);
1022    for b in bytes {
1023        out.push(HEX[(b >> 4) as usize] as char);
1024        out.push(HEX[(b & 0x0f) as usize] as char);
1025    }
1026    out
1027}
1028
1029fn validate_target_columns(columns: &[ColumnDef]) -> Result<()> {
1030    if columns.is_empty() {
1031        return Err(MongrelError::InvalidArgument(
1032            "view triggers require target_columns".into(),
1033        ));
1034    }
1035    let mut names = HashSet::new();
1036    let mut ids = HashSet::new();
1037    for column in columns {
1038        validate_name("trigger target column", &column.name)?;
1039        if !names.insert(&column.name) {
1040            return Err(MongrelError::InvalidArgument(format!(
1041                "duplicate trigger target column {:?}",
1042                column.name
1043            )));
1044        }
1045        if !ids.insert(column.id) {
1046            return Err(MongrelError::InvalidArgument(format!(
1047                "duplicate trigger target column id {}",
1048                column.id
1049            )));
1050        }
1051    }
1052    Ok(())
1053}
1054
1055fn validate_name(kind: &str, name: &str) -> Result<()> {
1056    if name.is_empty()
1057        || !name
1058            .chars()
1059            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
1060    {
1061        return Err(MongrelError::InvalidArgument(format!(
1062            "{kind} name {name:?} must be non-empty ASCII alphanumeric, '_' or '-'"
1063        )));
1064    }
1065    Ok(())
1066}