Skip to main content

pgevolve_core/ir/
trigger.rs

1//! `Trigger` — a Postgres trigger declared via `CREATE [CONSTRAINT] TRIGGER`.
2
3use serde::{Deserialize, Serialize};
4
5use crate::identifier::{Identifier, QualifiedName};
6use crate::ir::constraint::Deferrable;
7use crate::ir::default_expr::NormalizedExpr;
8use crate::ir::difference::Difference;
9use crate::ir::eq::{Equiv, field_difference};
10
11/// A Postgres trigger.
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct Trigger {
14    /// Schema-qualified trigger name. Schema mirrors the owning table's schema.
15    pub qname: QualifiedName,
16    /// Owning table (or view, for `INSTEAD OF` triggers).
17    pub table: QualifiedName,
18    /// When the trigger fires relative to the event.
19    pub timing: TriggerTiming,
20    /// Events that fire this trigger.
21    pub events: Vec<TriggerEvent>,
22    /// Row-level or statement-level.
23    pub level: TriggerLevel,
24    /// Optional `WHEN (condition)` predicate.
25    pub when_clause: Option<NormalizedExpr>,
26    /// Statement-level transition tables (`REFERENCING NEW TABLE AS n`).
27    pub transition_tables: Vec<TransitionTable>,
28    /// Qualified name of the trigger function.
29    pub function_qname: QualifiedName,
30    /// Literal string arguments passed to the trigger function.
31    pub function_args: Vec<String>,
32    /// `true` for `CREATE CONSTRAINT TRIGGER`.
33    pub is_constraint: bool,
34    /// Deferrability of constraint triggers.
35    pub deferrable: Deferrable,
36    /// Optional `COMMENT ON TRIGGER` text.
37    pub comment: Option<String>,
38}
39
40impl Equiv for Trigger {
41    fn differences(&self, other: &Self) -> Vec<Difference> {
42        let Self {
43            qname: _,
44            table: _,
45            timing: _,
46            events: _,
47            level: _,
48            when_clause: _,
49            transition_tables: _,
50            function_qname: _,
51            function_args: _,
52            is_constraint: _,
53            deferrable: _,
54            comment: _,
55        } = self;
56        let mut out = Vec::new();
57        out.extend(field_difference("qname", &self.qname, &other.qname));
58        out.extend(field_difference("table", &self.table, &other.table));
59        out.extend(field_difference(
60            "timing",
61            &format!("{:?}", self.timing),
62            &format!("{:?}", other.timing),
63        ));
64        out.extend(field_difference(
65            "events",
66            &format!("{:?}", self.events),
67            &format!("{:?}", other.events),
68        ));
69        out.extend(field_difference(
70            "level",
71            &format!("{:?}", self.level),
72            &format!("{:?}", other.level),
73        ));
74        out.extend(field_difference(
75            "when_clause",
76            &format!("{:?}", self.when_clause),
77            &format!("{:?}", other.when_clause),
78        ));
79        out.extend(field_difference(
80            "transition_tables",
81            &format!("{:?}", self.transition_tables),
82            &format!("{:?}", other.transition_tables),
83        ));
84        out.extend(field_difference(
85            "function_qname",
86            &self.function_qname,
87            &other.function_qname,
88        ));
89        out.extend(field_difference(
90            "function_args",
91            &format!("{:?}", self.function_args),
92            &format!("{:?}", other.function_args),
93        ));
94        out.extend(field_difference(
95            "is_constraint",
96            &self.is_constraint,
97            &other.is_constraint,
98        ));
99        out.extend(field_difference(
100            "deferrable",
101            &format!("{:?}", self.deferrable),
102            &format!("{:?}", other.deferrable),
103        ));
104        out.extend(field_difference(
105            "comment",
106            &format!("{:?}", self.comment),
107            &format!("{:?}", other.comment),
108        ));
109        out
110    }
111}
112
113/// When a trigger fires relative to the triggering event.
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
115#[serde(rename_all = "snake_case")]
116pub enum TriggerTiming {
117    /// Fires before the triggering event.
118    Before,
119    /// Fires after the triggering event.
120    After,
121    /// Replaces the triggering event (for views only).
122    InsteadOf,
123}
124
125/// An event that can trigger a trigger.
126#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
127#[serde(tag = "kind", rename_all = "snake_case")]
128pub enum TriggerEvent {
129    /// Trigger fires on `INSERT`.
130    Insert,
131    /// Trigger fires on `UPDATE` (optionally of specific columns).
132    Update {
133        /// Optional column list (`UPDATE OF col1, col2`). Empty = any column.
134        columns: Vec<Identifier>,
135    },
136    /// Trigger fires on `DELETE`.
137    Delete,
138    /// Trigger fires on `TRUNCATE`.
139    Truncate,
140}
141
142/// Whether a trigger fires for each row or each statement.
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
144#[serde(rename_all = "snake_case")]
145pub enum TriggerLevel {
146    /// Fires once per affected row.
147    Row,
148    /// Fires once per statement.
149    Statement,
150}
151
152/// A statement-level transition table reference.
153#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
154pub struct TransitionTable {
155    /// The name of the transition table.
156    pub name: Identifier,
157    /// Whether this is a NEW or OLD table.
158    pub kind: TransitionKind,
159}
160
161/// Whether a transition table is for NEW or OLD rows.
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
163#[serde(rename_all = "snake_case")]
164pub enum TransitionKind {
165    /// The `NEW` table containing inserted/updated rows.
166    NewTable,
167    /// The `OLD` table containing deleted/pre-updated rows.
168    OldTable,
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    use crate::ir::eq::Equiv;
175
176    fn id(s: &str) -> Identifier {
177        Identifier::from_unquoted(s).unwrap()
178    }
179
180    fn qn(schema: &str, name: &str) -> QualifiedName {
181        QualifiedName::new(id(schema), id(name))
182    }
183
184    fn trg(name: &str) -> Trigger {
185        Trigger {
186            qname: qn("app", name),
187            table: qn("app", "users"),
188            timing: TriggerTiming::Before,
189            events: vec![TriggerEvent::Insert],
190            level: TriggerLevel::Row,
191            when_clause: None,
192            transition_tables: vec![],
193            function_qname: qn("app", "fn"),
194            function_args: vec![],
195            is_constraint: false,
196            deferrable: Deferrable::NotDeferrable,
197            comment: None,
198        }
199    }
200
201    #[test]
202    fn identical_triggers_diff_empty() {
203        assert!(trg("t1").canonical_eq(&trg("t1")));
204    }
205
206    #[test]
207    fn different_timing_diffs_reports_timing() {
208        let a = trg("t1");
209        let mut b = trg("t1");
210        b.timing = TriggerTiming::After;
211        let d = a.differences(&b);
212        assert!(d.iter().any(|x| x.path == "timing"));
213    }
214
215    #[test]
216    fn different_events_diffs() {
217        let a = trg("t1");
218        let mut b = trg("t1");
219        b.events = vec![TriggerEvent::Delete];
220        let d = a.differences(&b);
221        assert!(d.iter().any(|x| x.path == "events"));
222    }
223}