Skip to main content

pgevolve_core/ir/
event_trigger.rs

1//! `EVENT TRIGGER` IR. Database-global (bare name, no schema), independently
2//! ownable; modeled like `Publication` (lenient owner, lenient drop in the diff).
3
4use serde::{Deserialize, Serialize};
5
6use crate::identifier::{Identifier, QualifiedName};
7use crate::ir::difference::Difference;
8use crate::ir::eq::{Equiv, field_difference};
9
10/// A `CREATE EVENT TRIGGER` object.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct EventTrigger {
13    /// Global object name — event triggers are not schema-qualified.
14    pub name: Identifier,
15    /// The DDL event the trigger fires on.
16    pub event: EventTriggerEvent,
17    /// `WHEN TAG IN (...)` command-tag filter; empty = no filter.
18    /// Canon sorts + dedupes this list.
19    pub tag_filter: Vec<String>,
20    /// Schema-qualified name of the `EXECUTE FUNCTION` function.
21    pub function: QualifiedName,
22    /// Fire state (`pg_event_trigger.evtenabled`).
23    pub enabled: EventTriggerEnabled,
24    /// Lenient owner: `None` = unmanaged (matches `Publication`).
25    pub owner: Option<Identifier>,
26    /// Optional comment.
27    pub comment: Option<String>,
28}
29
30impl Equiv for EventTrigger {
31    fn differences(&self, other: &Self) -> Vec<Difference> {
32        // Field-completeness guard: the compiler errors if a field is added
33        // without being handled below. Bindings are unused (read via `self`).
34        let Self {
35            name: _,
36            event: _,
37            tag_filter: _,
38            function: _,
39            enabled: _,
40            owner: _,
41            comment: _,
42        } = self;
43        let mut out = Vec::new();
44        out.extend(field_difference("name", &self.name, &other.name));
45        out.extend(field_difference(
46            "event",
47            &format!("{:?}", self.event),
48            &format!("{:?}", other.event),
49        ));
50        out.extend(field_difference(
51            "tag_filter",
52            &format!("{:?}", self.tag_filter),
53            &format!("{:?}", other.tag_filter),
54        ));
55        out.extend(field_difference(
56            "function",
57            &self.function,
58            &other.function,
59        ));
60        out.extend(field_difference(
61            "enabled",
62            &format!("{:?}", self.enabled),
63            &format!("{:?}", other.enabled),
64        ));
65        out.extend(field_difference(
66            "owner",
67            &format!("{:?}", self.owner),
68            &format!("{:?}", other.owner),
69        ));
70        out.extend(field_difference(
71            "comment",
72            &format!("{:?}", self.comment),
73            &format!("{:?}", other.comment),
74        ));
75        out
76    }
77}
78
79/// The DDL event an event trigger fires on (`pg_event_trigger.evtevent`).
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
81#[serde(rename_all = "snake_case")]
82pub enum EventTriggerEvent {
83    /// `ddl_command_start`
84    DdlCommandStart,
85    /// `ddl_command_end`
86    DdlCommandEnd,
87    /// `sql_drop`
88    SqlDrop,
89    /// `table_rewrite`
90    TableRewrite,
91}
92
93impl EventTriggerEvent {
94    /// The SQL keyword used in `ON <event>`.
95    #[must_use]
96    pub const fn sql(self) -> &'static str {
97        match self {
98            Self::DdlCommandStart => "ddl_command_start",
99            Self::DdlCommandEnd => "ddl_command_end",
100            Self::SqlDrop => "sql_drop",
101            Self::TableRewrite => "table_rewrite",
102        }
103    }
104
105    /// Parse from the SQL event name (lower-cased).
106    #[must_use]
107    pub fn from_sql(s: &str) -> Option<Self> {
108        match s {
109            "ddl_command_start" => Some(Self::DdlCommandStart),
110            "ddl_command_end" => Some(Self::DdlCommandEnd),
111            "sql_drop" => Some(Self::SqlDrop),
112            "table_rewrite" => Some(Self::TableRewrite),
113            _ => None,
114        }
115    }
116}
117
118/// Fire state (`pg_event_trigger.evtenabled`: `O`/`D`/`R`/`A`).
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
120#[serde(rename_all = "snake_case")]
121pub enum EventTriggerEnabled {
122    /// `O` — fires in origin/local sessions (the default).
123    Enabled,
124    /// `D` — never fires.
125    Disabled,
126    /// `R` — fires only when `session_replication_role = replica`.
127    Replica,
128    /// `A` — fires always (origin and replica).
129    Always,
130}
131
132impl EventTriggerEnabled {
133    /// Decode the single-char `pg_event_trigger.evtenabled` code.
134    #[must_use]
135    pub const fn from_pg_char(c: char) -> Option<Self> {
136        match c {
137            'O' => Some(Self::Enabled),
138            'D' => Some(Self::Disabled),
139            'R' => Some(Self::Replica),
140            'A' => Some(Self::Always),
141            _ => None,
142        }
143    }
144
145    /// The `ALTER EVENT TRIGGER name <clause>` body for this state.
146    #[must_use]
147    pub const fn alter_clause(self) -> &'static str {
148        match self {
149            Self::Enabled => "ENABLE",
150            Self::Disabled => "DISABLE",
151            Self::Replica => "ENABLE REPLICA",
152            Self::Always => "ENABLE ALWAYS",
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn event_sql_roundtrips() {
163        for e in [
164            EventTriggerEvent::DdlCommandStart,
165            EventTriggerEvent::DdlCommandEnd,
166            EventTriggerEvent::SqlDrop,
167            EventTriggerEvent::TableRewrite,
168        ] {
169            assert_eq!(EventTriggerEvent::from_sql(e.sql()), Some(e));
170        }
171        assert_eq!(EventTriggerEvent::from_sql("bogus"), None);
172    }
173
174    #[test]
175    fn enabled_decodes_pg_chars() {
176        assert_eq!(
177            EventTriggerEnabled::from_pg_char('O'),
178            Some(EventTriggerEnabled::Enabled)
179        );
180        assert_eq!(
181            EventTriggerEnabled::from_pg_char('D'),
182            Some(EventTriggerEnabled::Disabled)
183        );
184        assert_eq!(
185            EventTriggerEnabled::from_pg_char('R'),
186            Some(EventTriggerEnabled::Replica)
187        );
188        assert_eq!(
189            EventTriggerEnabled::from_pg_char('A'),
190            Some(EventTriggerEnabled::Always)
191        );
192        assert_eq!(EventTriggerEnabled::from_pg_char('x'), None);
193    }
194
195    #[test]
196    fn alter_clauses() {
197        assert_eq!(EventTriggerEnabled::Disabled.alter_clause(), "DISABLE");
198        assert_eq!(
199            EventTriggerEnabled::Replica.alter_clause(),
200            "ENABLE REPLICA"
201        );
202        assert_eq!(EventTriggerEnabled::Always.alter_clause(), "ENABLE ALWAYS");
203        assert_eq!(EventTriggerEnabled::Enabled.alter_clause(), "ENABLE");
204    }
205}