use std::fmt::{Display, Formatter};
#[cfg(any(feature = "postgres", feature = "sqlite"))]
use rorm_declaration::imr::Annotation;
#[cfg(feature = "sqlite")]
use rorm_declaration::imr::DbType;
#[cfg(feature = "sqlite")]
use crate::DBImpl;
#[cfg(any(feature = "sqlite", feature = "postgres"))]
use crate::Value;
pub enum SQLCreateTriggerPointInTime {
After,
Before,
InsteadOf,
}
impl Display for SQLCreateTriggerPointInTime {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SQLCreateTriggerPointInTime::After => write!(f, "AFTER"),
SQLCreateTriggerPointInTime::Before => write!(f, "BEFORE"),
SQLCreateTriggerPointInTime::InsteadOf => write!(f, "INSTEAD OF"),
}
}
}
pub enum SQLCreateTriggerOperation {
Delete,
Insert,
Update {
columns: Option<Vec<String>>,
},
}
impl Display for SQLCreateTriggerOperation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SQLCreateTriggerOperation::Delete => write!(f, "DELETE"),
SQLCreateTriggerOperation::Insert => write!(f, "INSERT"),
SQLCreateTriggerOperation::Update { columns: None } => write!(f, "UPDATE"),
SQLCreateTriggerOperation::Update { columns: Some(c) } => {
write!(f, "UPDATE OF {}", c.join(","))
}
}
}
}
#[cfg(feature = "postgres")]
pub(crate) fn trigger_annotation_to_trigger_postgres(
annotation: &Annotation,
table_name: &str,
column_name: &str,
statements: &mut Vec<(String, Vec<Value>)>,
) {
if annotation == &Annotation::AutoUpdateTime {
statements.push(
(
format!(
"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';"
),
vec![],
)
);
statements.push((
format!(
"DROP TRIGGER IF EXISTS {table_name}_{column_name}_auto_update_time_update ON \"{table_name}\";"
),
vec![],
));
statements.push(
(
format!(
"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();"
),
vec![],
)
);
}
}
#[cfg(feature = "sqlite")]
pub(crate) fn trigger_annotation_to_trigger_sqlite(
annotation: &Annotation,
db_type: &DbType,
table_name: &str,
column_name: &str,
statements: &mut Vec<(String, Vec<Value>)>,
) {
if annotation == &Annotation::AutoUpdateTime {
let update_statement = format!(
"UPDATE {} SET {} = {} WHERE ROWID = NEW.ROWID;",
table_name,
column_name,
match db_type {
DbType::Date => "CURRENT_DATE",
DbType::DateTime => "CURRENT_TIMESTAMP",
DbType::Timestamp => "CURRENT_TIMESTAMP",
DbType::Time => "CURRENT_TIME",
_ => "",
}
);
statements.push((
DBImpl::SQLite
.create_trigger(
format!("{table_name}_{column_name}_auto_update_time").as_str(),
table_name,
Some(SQLCreateTriggerPointInTime::After),
SQLCreateTriggerOperation::Update { columns: None },
)
.for_each_row()
.if_not_exists()
.add_statement(update_statement)
.build(),
vec![],
))
}
}
pub struct SQLCreateTrigger {
pub(crate) name: String,
pub(crate) table_name: String,
pub(crate) if_not_exists: bool,
pub(crate) point_in_time: Option<SQLCreateTriggerPointInTime>,
pub(crate) operation: SQLCreateTriggerOperation,
pub(crate) statements: Vec<String>,
pub(crate) for_each_row: bool,
}
impl SQLCreateTrigger {
pub fn if_not_exists(mut self) -> Self {
self.if_not_exists = true;
self
}
pub fn add_statement(mut self, statement: String) -> Self {
self.statements.push(statement);
self
}
pub fn for_each_row(mut self) -> Self {
self.for_each_row = true;
self
}
pub fn build(self) -> String {
format!(
"CREATE TRIGGER {} {} {} {} ON {}{} BEGIN {} END;",
if self.if_not_exists {
"IF NOT EXISTS"
} else {
""
},
self.name,
match self.point_in_time {
None => "".to_string(),
Some(s) => s.to_string(),
},
self.operation,
self.table_name,
match self.for_each_row {
true => " FOR EACH ROW",
false => "",
},
self.statements.join(" "),
)
}
}