rorm_sql/
create_trigger.rs

1use std::fmt::{Display, Formatter};
2
3#[cfg(any(feature = "postgres", feature = "sqlite"))]
4use rorm_declaration::imr::Annotation;
5#[cfg(feature = "sqlite")]
6use rorm_declaration::imr::DbType;
7
8#[cfg(feature = "sqlite")]
9use crate::DBImpl;
10#[cfg(any(feature = "sqlite", feature = "postgres"))]
11use crate::Value;
12
13/**
14Representation of a point in time definition of a create trigger statement
15*/
16pub enum SQLCreateTriggerPointInTime {
17    /// Trigger AFTER operation
18    After,
19    /// Trigger BEFORE operation
20    Before,
21    /// Trigger INSTEAD OF operation
22    InsteadOf,
23}
24
25impl Display for SQLCreateTriggerPointInTime {
26    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
27        match self {
28            SQLCreateTriggerPointInTime::After => write!(f, "AFTER"),
29            SQLCreateTriggerPointInTime::Before => write!(f, "BEFORE"),
30            SQLCreateTriggerPointInTime::InsteadOf => write!(f, "INSTEAD OF"),
31        }
32    }
33}
34
35/**
36Representation of the operation to execute the trigger on
37*/
38pub enum SQLCreateTriggerOperation {
39    /// Execute a DELETE operation
40    Delete,
41    /// Execute an INSERT operation
42    Insert,
43    /// Execute an UPDATE operation
44    Update {
45        /// Columns to update
46        columns: Option<Vec<String>>,
47    },
48}
49
50impl Display for SQLCreateTriggerOperation {
51    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
52        match self {
53            SQLCreateTriggerOperation::Delete => write!(f, "DELETE"),
54            SQLCreateTriggerOperation::Insert => write!(f, "INSERT"),
55            SQLCreateTriggerOperation::Update { columns: None } => write!(f, "UPDATE"),
56            SQLCreateTriggerOperation::Update { columns: Some(c) } => {
57                write!(f, "UPDATE OF {}", c.join(","))
58            }
59        }
60    }
61}
62
63// TODO: Make this more beautiful :D
64#[cfg(feature = "postgres")]
65pub(crate) fn trigger_annotation_to_trigger_postgres(
66    annotation: &Annotation,
67    table_name: &str,
68    column_name: &str,
69    statements: &mut Vec<(String, Vec<Value>)>,
70) {
71    if annotation == &Annotation::AutoUpdateTime {
72        statements.push(
73            (
74                format!(
75                    "CREATE OR REPLACE FUNCTION {table_name}_{column_name}_auto_update_time_update_procedure() RETURNS TRIGGER AS $$ BEGIN NEW.{column_name} = now(); RETURN NEW; END; $$ language 'plpgsql';"
76                ),
77                vec![],
78            )
79        );
80        statements.push((
81            format!(
82                "DROP TRIGGER IF EXISTS {table_name}_{column_name}_auto_update_time_update ON \"{table_name}\";"
83            ),
84            vec![],
85        ));
86        statements.push(
87            (
88                format!(
89                    "CREATE TRIGGER {table_name}_{column_name}_auto_update_time_update BEFORE UPDATE ON \"{table_name}\" FOR EACH ROW WHEN (OLD IS DISTINCT FROM NEW) EXECUTE PROCEDURE {table_name}_{column_name}_auto_update_time_update_procedure();"
90                ),
91                vec![],
92            )
93        );
94    }
95}
96
97// TODO: Make this more beautiful :D
98#[cfg(feature = "sqlite")]
99pub(crate) fn trigger_annotation_to_trigger_sqlite(
100    annotation: &Annotation,
101    db_type: &DbType,
102    table_name: &str,
103    column_name: &str,
104    statements: &mut Vec<(String, Vec<Value>)>,
105) {
106    if annotation == &Annotation::AutoUpdateTime {
107        let update_statement = format!(
108            "UPDATE {} SET {} = {} WHERE ROWID = NEW.ROWID;",
109            table_name,
110            column_name,
111            match db_type {
112                DbType::Date => "CURRENT_DATE",
113                DbType::DateTime => "CURRENT_TIMESTAMP",
114                DbType::Timestamp => "CURRENT_TIMESTAMP",
115                DbType::Time => "CURRENT_TIME",
116                _ => "",
117            }
118        );
119        statements.push((
120            DBImpl::SQLite
121                .create_trigger(
122                    format!("{table_name}_{column_name}_auto_update_time").as_str(),
123                    table_name,
124                    Some(SQLCreateTriggerPointInTime::After),
125                    SQLCreateTriggerOperation::Update { columns: None },
126                )
127                .for_each_row()
128                .if_not_exists()
129                .add_statement(update_statement)
130                .build(),
131            vec![],
132        ))
133    }
134}
135
136/**
137Representation of a trigger.
138*/
139pub struct SQLCreateTrigger {
140    pub(crate) name: String,
141    pub(crate) table_name: String,
142    pub(crate) if_not_exists: bool,
143    pub(crate) point_in_time: Option<SQLCreateTriggerPointInTime>,
144    pub(crate) operation: SQLCreateTriggerOperation,
145    pub(crate) statements: Vec<String>,
146    pub(crate) for_each_row: bool,
147}
148
149impl SQLCreateTrigger {
150    /**
151    Create the trigger only, if it does not exists
152    */
153    pub fn if_not_exists(mut self) -> Self {
154        self.if_not_exists = true;
155        self
156    }
157
158    /**
159    Adds a statement to a create trigger operation
160    */
161    pub fn add_statement(mut self, statement: String) -> Self {
162        self.statements.push(statement);
163        self
164    }
165
166    /**
167    Executes the given trigger statement for each row individually.
168    */
169    pub fn for_each_row(mut self) -> Self {
170        self.for_each_row = true;
171        self
172    }
173
174    /**
175    Generate the resulting SQL string
176    */
177    pub fn build(self) -> String {
178        format!(
179            "CREATE TRIGGER {} {} {} {} ON {}{} BEGIN {} END;",
180            if self.if_not_exists {
181                "IF NOT EXISTS"
182            } else {
183                ""
184            },
185            self.name,
186            match self.point_in_time {
187                None => "".to_string(),
188                Some(s) => s.to_string(),
189            },
190            self.operation,
191            self.table_name,
192            match self.for_each_row {
193                true => " FOR EACH ROW",
194                false => "",
195            },
196            self.statements.join(" "),
197        )
198    }
199}