use crate::backend::Backend;
use crate::error::{QueryError, Result};
use crate::ident;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TriggerTime {
Before,
After,
InsteadOf,
}
impl TriggerTime {
fn as_sql(&self) -> &'static str {
match self {
TriggerTime::Before => "BEFORE",
TriggerTime::After => "AFTER",
TriggerTime::InsteadOf => "INSTEAD OF",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TriggerEvent {
Insert,
Update,
Delete,
}
impl TriggerEvent {
fn as_sql(&self) -> &'static str {
match self {
TriggerEvent::Insert => "INSERT",
TriggerEvent::Update => "UPDATE",
TriggerEvent::Delete => "DELETE",
}
}
}
#[derive(Debug, Clone)]
pub struct CreateTrigger {
backend: Backend,
name: String,
time: Option<TriggerTime>,
event: Option<TriggerEvent>,
table: String,
when_condition: Option<String>,
body: Option<String>, pg_function: Option<String>, if_not_exists: bool,
}
impl CreateTrigger {
pub fn new(backend: Backend, name: &str) -> Self {
Self {
backend,
name: name.to_string(),
time: None,
event: None,
table: String::new(),
when_condition: None,
body: None,
pg_function: None,
if_not_exists: false,
}
}
pub fn before(mut self) -> Self {
self.time = Some(TriggerTime::Before);
self
}
pub fn after(mut self) -> Self {
self.time = Some(TriggerTime::After);
self
}
pub fn instead_of(mut self) -> Self {
self.time = Some(TriggerTime::InsteadOf);
self
}
pub fn insert(mut self) -> Self {
self.event = Some(TriggerEvent::Insert);
self
}
pub fn update(mut self) -> Self {
self.event = Some(TriggerEvent::Update);
self
}
pub fn delete(mut self) -> Self {
self.event = Some(TriggerEvent::Delete);
self
}
pub fn on_table<S: Into<String>>(mut self, table: S) -> Self {
self.table = table.into();
self
}
pub fn when<S: Into<String>>(mut self, condition: S) -> Self {
self.when_condition = Some(condition.into());
self
}
pub fn body<S: Into<String>>(mut self, body: S) -> Self {
self.body = Some(body.into());
self
}
pub fn execute_function<S: Into<String>>(mut self, fn_name: S) -> Self {
self.pg_function = Some(fn_name.into());
self
}
pub fn if_not_exists(mut self) -> Self {
self.if_not_exists = true;
self
}
pub fn to_sql(&self) -> Result<String> {
ident::validate(&self.name)?;
if self.table.is_empty() {
return Err(QueryError::InvalidIdentifier("on_table required".into()));
}
ident::validate(&self.table)?;
let time = self.time.ok_or_else(|| {
QueryError::InvalidIdentifier("trigger time required (.before/.after/.instead_of)".into())
})?;
let event = self.event.ok_or_else(|| {
QueryError::InvalidIdentifier("trigger event required (.insert/.update/.delete)".into())
})?;
let qname = self.backend.quote_ident(&self.name);
let qtable = self.backend.quote_ident(&self.table);
let head = if self.if_not_exists {
match self.backend {
Backend::MySql => "CREATE TRIGGER",
_ => "CREATE TRIGGER IF NOT EXISTS",
}
} else {
"CREATE TRIGGER"
};
match self.backend {
Backend::Postgres => {
let fn_name = self.pg_function.as_ref().ok_or_else(|| {
QueryError::InvalidIdentifier(
"PG: .execute_function() required (PG ejecuta función, no body inline)".into(),
)
})?;
ident::validate(fn_name)?;
let mut sql = format!(
"{} {} {} {} ON {} FOR EACH ROW",
head, qname, time.as_sql(), event.as_sql(), qtable
);
if let Some(when) = &self.when_condition {
sql.push_str(&format!(" WHEN ({})", when));
}
sql.push_str(&format!(" EXECUTE FUNCTION {}()", self.backend.quote_ident(fn_name)));
Ok(sql)
}
Backend::MySql => {
let body = self.body.as_ref().ok_or_else(|| {
QueryError::InvalidIdentifier("MySQL: .body() required".into())
})?;
Ok(format!(
"{} {} {} {} ON {} FOR EACH ROW BEGIN {}; END",
head, qname, time.as_sql(), event.as_sql(), qtable, body.trim_end_matches(';')
))
}
Backend::Sqlite => {
let body = self.body.as_ref().ok_or_else(|| {
QueryError::InvalidIdentifier("SQLite: .body() required".into())
})?;
let mut sql = format!(
"{} {} {} {} ON {} FOR EACH ROW",
head, qname, time.as_sql(), event.as_sql(), qtable
);
if let Some(when) = &self.when_condition {
sql.push_str(&format!(" WHEN {}", when));
}
sql.push_str(&format!(" BEGIN {}; END", body.trim_end_matches(';')));
Ok(sql)
}
}
}
}
#[derive(Debug, Clone)]
pub struct DropTrigger {
backend: Backend,
name: String,
table: Option<String>, if_exists: bool,
}
impl DropTrigger {
pub fn new(backend: Backend, name: &str) -> Self {
Self {
backend,
name: name.to_string(),
table: None,
if_exists: false,
}
}
pub fn if_exists(mut self) -> Self {
self.if_exists = true;
self
}
pub fn on_table<S: Into<String>>(mut self, t: S) -> Self {
self.table = Some(t.into());
self
}
pub fn to_sql(&self) -> Result<String> {
ident::validate(&self.name)?;
let mut sql = String::from("DROP TRIGGER");
if self.if_exists {
sql.push_str(" IF EXISTS");
}
sql.push(' ');
sql.push_str(&self.backend.quote_ident(&self.name));
if self.backend == Backend::Postgres {
let t = self.table.as_ref().ok_or_else(|| {
QueryError::InvalidIdentifier("PG: DROP TRIGGER requiere .on_table()".into())
})?;
ident::validate(t)?;
sql.push_str(&format!(" ON {}", self.backend.quote_ident(t)));
}
Ok(sql)
}
}
#[derive(Debug, Clone)]
pub struct CreateEvent {
backend: Backend,
name: String,
schedule: Option<String>, body: Option<String>,
if_not_exists: bool,
}
impl CreateEvent {
pub fn new(backend: Backend, name: &str) -> Self {
Self {
backend,
name: name.to_string(),
schedule: None,
body: None,
if_not_exists: false,
}
}
pub fn if_not_exists(mut self) -> Self {
self.if_not_exists = true;
self
}
pub fn schedule<S: Into<String>>(mut self, expr: S) -> Self {
self.schedule = Some(expr.into());
self
}
pub fn body<S: Into<String>>(mut self, body: S) -> Self {
self.body = Some(body.into());
self
}
pub fn to_sql(&self) -> Result<String> {
if self.backend != Backend::MySql {
return Err(QueryError::InvalidOperator(
"events solo en MySQL/MariaDB".to_string(),
));
}
ident::validate(&self.name)?;
let schedule = self.schedule.as_ref().ok_or_else(|| {
QueryError::InvalidIdentifier(".schedule() required".into())
})?;
let body = self.body.as_ref().ok_or_else(|| {
QueryError::InvalidIdentifier(".body() required".into())
})?;
let head = if self.if_not_exists {
"CREATE EVENT IF NOT EXISTS"
} else {
"CREATE EVENT"
};
Ok(format!(
"{} {} ON SCHEDULE {} DO {}",
head,
self.backend.quote_ident(&self.name),
schedule,
body.trim_end_matches(';')
))
}
}
#[derive(Debug, Clone)]
pub struct DropEvent {
backend: Backend,
name: String,
if_exists: bool,
}
impl DropEvent {
pub fn new(backend: Backend, name: &str) -> Self {
Self { backend, name: name.to_string(), if_exists: false }
}
pub fn if_exists(mut self) -> Self {
self.if_exists = true;
self
}
pub fn to_sql(&self) -> Result<String> {
if self.backend != Backend::MySql {
return Err(QueryError::InvalidOperator(
"events solo en MySQL/MariaDB".to_string(),
));
}
ident::validate(&self.name)?;
let mut sql = String::from("DROP EVENT");
if self.if_exists {
sql.push_str(" IF EXISTS");
}
sql.push(' ');
sql.push_str(&self.backend.quote_ident(&self.name));
Ok(sql)
}
}