Added support for SQLite triggers (#2037)
Some checks failed
license / Release Audit Tool (RAT) (push) Has been cancelled
Rust / codestyle (push) Has been cancelled
Rust / lint (push) Has been cancelled
Rust / benchmark-lint (push) Has been cancelled
Rust / compile (push) Has been cancelled
Rust / docs (push) Has been cancelled
Rust / compile-no-std (push) Has been cancelled
Rust / test (beta) (push) Has been cancelled
Rust / test (nightly) (push) Has been cancelled
Rust / test (stable) (push) Has been cancelled

Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
This commit is contained in:
Luca Cappelletti 2025-10-11 13:16:56 +02:00 committed by GitHub
parent cc595cfd84
commit 4490c8c55c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 374 additions and 46 deletions

View file

@ -2922,6 +2922,26 @@ impl Spanned for RenameTableNameKind {
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
/// Whether the syntax used for the trigger object (ROW or STATEMENT) is `FOR` or `FOR EACH`.
pub enum TriggerObjectKind {
/// The `FOR` syntax is used.
For(TriggerObject),
/// The `FOR EACH` syntax is used.
ForEach(TriggerObject),
}
impl Display for TriggerObjectKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TriggerObjectKind::For(obj) => write!(f, "FOR {obj}"),
TriggerObjectKind::ForEach(obj) => write!(f, "FOR EACH {obj}"),
}
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@ -2943,6 +2963,23 @@ pub struct CreateTrigger {
///
/// [MsSql](https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql?view=sql-server-ver16#arguments)
pub or_alter: bool,
/// True if this is a temporary trigger.
///
/// Examples:
///
/// ```sql
/// CREATE TEMP TRIGGER trigger_name
/// ```
///
/// or
///
/// ```sql
/// CREATE TEMPORARY TRIGGER trigger_name;
/// CREATE TEMP TRIGGER trigger_name;
/// ```
///
/// [SQLite](https://sqlite.org/lang_createtrigger.html#temp_triggers_on_non_temp_tables)
pub temporary: bool,
/// The `OR REPLACE` clause is used to re-create the trigger if it already exists.
///
/// Example:
@ -2987,6 +3024,8 @@ pub struct CreateTrigger {
/// ```
pub period: TriggerPeriod,
/// Whether the trigger period was specified before the target table name.
/// This does not refer to whether the period is BEFORE, AFTER, or INSTEAD OF,
/// but rather the position of the period clause in relation to the table name.
///
/// ```sql
/// -- period_before_table == true: Postgres, MySQL, and standard SQL
@ -3006,9 +3045,9 @@ pub struct CreateTrigger {
pub referencing: Vec<TriggerReferencing>,
/// This specifies whether the trigger function should be fired once for
/// every row affected by the trigger event, or just once per SQL statement.
pub trigger_object: TriggerObject,
/// Whether to include the `EACH` term of the `FOR EACH`, as it is optional syntax.
pub include_each: bool,
/// This is optional in some SQL dialects, such as SQLite, and if not specified, in
/// those cases, the implied default is `FOR EACH ROW`.
pub trigger_object: Option<TriggerObjectKind>,
/// Triggering conditions
pub condition: Option<Expr>,
/// Execute logic block
@ -3025,6 +3064,7 @@ impl Display for CreateTrigger {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
@ -3036,7 +3076,6 @@ impl Display for CreateTrigger {
referencing,
trigger_object,
condition,
include_each,
exec_body,
statements_as,
statements,
@ -3044,7 +3083,8 @@ impl Display for CreateTrigger {
} = self;
write!(
f,
"CREATE {or_alter}{or_replace}{is_constraint}TRIGGER {name} ",
"CREATE {temporary}{or_alter}{or_replace}{is_constraint}TRIGGER {name} ",
temporary = if *temporary { "TEMPORARY " } else { "" },
or_alter = if *or_alter { "OR ALTER " } else { "" },
or_replace = if *or_replace { "OR REPLACE " } else { "" },
is_constraint = if *is_constraint { "CONSTRAINT " } else { "" },
@ -3076,10 +3116,8 @@ impl Display for CreateTrigger {
write!(f, " REFERENCING {}", display_separated(referencing, " "))?;
}
if *include_each {
write!(f, " FOR EACH {trigger_object}")?;
} else if exec_body.is_some() {
write!(f, " FOR {trigger_object}")?;
if let Some(trigger_object) = trigger_object {
write!(f, " {trigger_object}")?;
}
if let Some(condition) = condition {
write!(f, " WHEN {condition}")?;

View file

@ -70,8 +70,8 @@ pub use self::ddl::{
IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck,
NullsDistinctOption, Owner, Partition, ProcedureParam, ReferentialAction, RenameTableNameKind,
ReplicaIdentity, TagsColumnOption, Truncate, UserDefinedTypeCompositeAttributeDef,
UserDefinedTypeRepresentation, ViewColumnDef,
ReplicaIdentity, TagsColumnOption, TriggerObjectKind, Truncate,
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef,
};
pub use self::dml::{Delete, Insert, Update};
pub use self::operator::{BinaryOperator, UnaryOperator};

View file

@ -18,7 +18,7 @@
use crate::ast::helpers::attached_token::AttachedToken;
use crate::ast::{
BeginEndStatements, ConditionalStatementBlock, ConditionalStatements, CreateTrigger,
GranteesType, IfStatement, Statement, TriggerObject,
GranteesType, IfStatement, Statement,
};
use crate::dialect::Dialect;
use crate::keywords::{self, Keyword};
@ -254,6 +254,7 @@ impl MsSqlDialect {
Ok(CreateTrigger {
or_alter,
temporary: false,
or_replace: false,
is_constraint: false,
name,
@ -263,8 +264,7 @@ impl MsSqlDialect {
table_name,
referenced_table_name: None,
referencing: Vec::new(),
trigger_object: TriggerObject::Statement,
include_each: false,
trigger_object: None,
condition: None,
exec_body: None,
statements_as: true,

View file

@ -4753,9 +4753,9 @@ impl<'a> Parser<'a> {
} else if self.parse_keyword(Keyword::DOMAIN) {
self.parse_create_domain()
} else if self.parse_keyword(Keyword::TRIGGER) {
self.parse_create_trigger(or_alter, or_replace, false)
self.parse_create_trigger(temporary, or_alter, or_replace, false)
} else if self.parse_keywords(&[Keyword::CONSTRAINT, Keyword::TRIGGER]) {
self.parse_create_trigger(or_alter, or_replace, true)
self.parse_create_trigger(temporary, or_alter, or_replace, true)
} else if self.parse_keyword(Keyword::MACRO) {
self.parse_create_macro(or_replace, temporary)
} else if self.parse_keyword(Keyword::SECRET) {
@ -5551,7 +5551,8 @@ impl<'a> Parser<'a> {
/// DROP TRIGGER [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ]
/// ```
pub fn parse_drop_trigger(&mut self) -> Result<Statement, ParserError> {
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
if !dialect_of!(self is PostgreSqlDialect | SQLiteDialect | GenericDialect | MySqlDialect | MsSqlDialect)
{
self.prev_token();
return self.expected("an object type after DROP", self.peek_token());
}
@ -5579,11 +5580,13 @@ impl<'a> Parser<'a> {
pub fn parse_create_trigger(
&mut self,
temporary: bool,
or_alter: bool,
or_replace: bool,
is_constraint: bool,
) -> Result<Statement, ParserError> {
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
if !dialect_of!(self is PostgreSqlDialect | SQLiteDialect | GenericDialect | MySqlDialect | MsSqlDialect)
{
self.prev_token();
return self.expected("an object type after CREATE", self.peek_token());
}
@ -5610,14 +5613,25 @@ impl<'a> Parser<'a> {
}
}
self.expect_keyword_is(Keyword::FOR)?;
let include_each = self.parse_keyword(Keyword::EACH);
let trigger_object =
match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? {
Keyword::ROW => TriggerObject::Row,
Keyword::STATEMENT => TriggerObject::Statement,
_ => unreachable!(),
};
let trigger_object = if self.parse_keyword(Keyword::FOR) {
let include_each = self.parse_keyword(Keyword::EACH);
let trigger_object =
match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? {
Keyword::ROW => TriggerObject::Row,
Keyword::STATEMENT => TriggerObject::Statement,
_ => unreachable!(),
};
Some(if include_each {
TriggerObjectKind::ForEach(trigger_object)
} else {
TriggerObjectKind::For(trigger_object)
})
} else {
let _ = self.parse_keyword(Keyword::FOR);
None
};
let condition = self
.parse_keyword(Keyword::WHEN)
@ -5632,8 +5646,9 @@ impl<'a> Parser<'a> {
statements = Some(self.parse_conditional_statements(&[Keyword::END])?);
}
Ok(Statement::CreateTrigger(CreateTrigger {
Ok(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
@ -5644,13 +5659,13 @@ impl<'a> Parser<'a> {
referenced_table_name,
referencing,
trigger_object,
include_each,
condition,
exec_body,
statements_as: false,
statements,
characteristics,
}))
}
.into())
}
pub fn parse_trigger_period(&mut self) -> Result<TriggerPeriod, ParserError> {

View file

@ -2388,6 +2388,7 @@ fn parse_create_trigger() {
create_stmt,
Statement::CreateTrigger(CreateTrigger {
or_alter: true,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("reminder1")]),
@ -2397,8 +2398,7 @@ fn parse_create_trigger() {
table_name: ObjectName::from(vec![Ident::new("Sales"), Ident::new("Customer")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Statement,
include_each: false,
trigger_object: None,
condition: None,
exec_body: None,
statements_as: true,

View file

@ -4018,6 +4018,7 @@ fn parse_create_trigger() {
create_stmt,
Statement::CreateTrigger(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
@ -4027,8 +4028,7 @@ fn parse_create_trigger() {
table_name: ObjectName::from(vec![Ident::new("emp")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Row,
include_each: true,
trigger_object: Some(TriggerObjectKind::ForEach(TriggerObject::Row)),
condition: None,
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,

View file

@ -5636,6 +5636,7 @@ fn parse_create_simple_before_insert_trigger() {
let sql = "CREATE TRIGGER check_insert BEFORE INSERT ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_insert";
let expected = Statement::CreateTrigger(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("check_insert")]),
@ -5645,8 +5646,7 @@ fn parse_create_simple_before_insert_trigger() {
table_name: ObjectName::from(vec![Ident::new("accounts")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Row,
include_each: true,
trigger_object: Some(TriggerObjectKind::ForEach(TriggerObject::Row)),
condition: None,
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,
@ -5668,6 +5668,7 @@ fn parse_create_after_update_trigger_with_condition() {
let sql = "CREATE TRIGGER check_update AFTER UPDATE ON accounts FOR EACH ROW WHEN (NEW.balance > 10000) EXECUTE FUNCTION check_account_update";
let expected = Statement::CreateTrigger(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("check_update")]),
@ -5677,8 +5678,7 @@ fn parse_create_after_update_trigger_with_condition() {
table_name: ObjectName::from(vec![Ident::new("accounts")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Row,
include_each: true,
trigger_object: Some(TriggerObjectKind::ForEach(TriggerObject::Row)),
condition: Some(Expr::Nested(Box::new(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("NEW"),
@ -5707,6 +5707,7 @@ fn parse_create_instead_of_delete_trigger() {
let sql = "CREATE TRIGGER check_delete INSTEAD OF DELETE ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_deletes";
let expected = Statement::CreateTrigger(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("check_delete")]),
@ -5716,8 +5717,7 @@ fn parse_create_instead_of_delete_trigger() {
table_name: ObjectName::from(vec![Ident::new("accounts")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Row,
include_each: true,
trigger_object: Some(TriggerObjectKind::ForEach(TriggerObject::Row)),
condition: None,
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,
@ -5739,6 +5739,7 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() {
let sql = "CREATE CONSTRAINT TRIGGER check_multiple_events BEFORE INSERT OR UPDATE OR DELETE ON accounts DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION check_account_changes";
let expected = Statement::CreateTrigger(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: true,
name: ObjectName::from(vec![Ident::new("check_multiple_events")]),
@ -5752,8 +5753,7 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() {
table_name: ObjectName::from(vec![Ident::new("accounts")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Row,
include_each: true,
trigger_object: Some(TriggerObjectKind::ForEach(TriggerObject::Row)),
condition: None,
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,
@ -5779,6 +5779,7 @@ fn parse_create_trigger_with_referencing() {
let sql = "CREATE TRIGGER check_referencing BEFORE INSERT ON accounts REFERENCING NEW TABLE AS new_accounts OLD TABLE AS old_accounts FOR EACH ROW EXECUTE FUNCTION check_account_referencing";
let expected = Statement::CreateTrigger(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("check_referencing")]),
@ -5799,8 +5800,7 @@ fn parse_create_trigger_with_referencing() {
transition_relation_name: ObjectName::from(vec![Ident::new("old_accounts")]),
},
],
trigger_object: TriggerObject::Row,
include_each: true,
trigger_object: Some(TriggerObjectKind::ForEach(TriggerObject::Row)),
condition: None,
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,
@ -5826,7 +5826,7 @@ fn parse_create_trigger_invalid_cases() {
let invalid_cases = vec![
(
"CREATE TRIGGER check_update BEFORE UPDATE ON accounts FUNCTION check_account_update",
"Expected: FOR, found: FUNCTION"
"Expected: an SQL statement, found: FUNCTION"
),
(
"CREATE TRIGGER check_update TOMORROW UPDATE ON accounts EXECUTE FUNCTION check_account_update",
@ -6095,6 +6095,7 @@ fn parse_trigger_related_functions() {
create_trigger,
Statement::CreateTrigger(CreateTrigger {
or_alter: false,
temporary: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
@ -6104,8 +6105,7 @@ fn parse_trigger_related_functions() {
table_name: ObjectName::from(vec![Ident::new("emp")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Row,
include_each: true,
trigger_object: Some(TriggerObjectKind::ForEach(TriggerObject::Row)),
condition: None,
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,

View file

@ -610,6 +610,281 @@ fn test_update_delete_limit() {
}
}
#[test]
fn test_create_trigger() {
let statement1 = "CREATE TRIGGER trg_inherit_asset_models AFTER INSERT ON assets FOR EACH ROW BEGIN INSERT INTO users (name) SELECT pam.name FROM users AS pam; END";
match sqlite().verified_stmt(statement1) {
Statement::CreateTrigger(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table,
events,
table_name,
referenced_table_name,
referencing,
trigger_object,
condition,
exec_body: _,
statements_as,
statements: _,
characteristics,
}) => {
assert!(!or_alter);
assert!(!temporary);
assert!(!or_replace);
assert!(!is_constraint);
assert_eq!(name.to_string(), "trg_inherit_asset_models");
assert_eq!(period, TriggerPeriod::After);
assert!(period_before_table);
assert_eq!(events, vec![TriggerEvent::Insert]);
assert_eq!(table_name.to_string(), "assets");
assert!(referenced_table_name.is_none());
assert!(referencing.is_empty());
assert_eq!(
trigger_object,
Some(TriggerObjectKind::ForEach(TriggerObject::Row))
);
assert!(condition.is_none());
assert!(!statements_as);
assert!(characteristics.is_none());
}
_ => unreachable!("Expected CREATE TRIGGER statement"),
}
// Here we check that the variant of CREATE TRIGGER that omits the `FOR EACH ROW` clause,
// which in SQLite may be implicitly assumed, is parsed correctly.
let statement2 = "CREATE TRIGGER log_new_user AFTER INSERT ON users BEGIN INSERT INTO user_log (user_id, action, timestamp) VALUES (NEW.id, 'created', datetime('now')); END";
match sqlite().verified_stmt(statement2) {
Statement::CreateTrigger(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table,
events,
table_name,
referenced_table_name,
referencing,
trigger_object,
condition,
exec_body: _,
statements_as,
statements: _,
characteristics,
}) => {
assert!(!or_alter);
assert!(!temporary);
assert!(!or_replace);
assert!(!is_constraint);
assert_eq!(name.to_string(), "log_new_user");
assert_eq!(period, TriggerPeriod::After);
assert!(period_before_table);
assert_eq!(events, vec![TriggerEvent::Insert]);
assert_eq!(table_name.to_string(), "users");
assert!(referenced_table_name.is_none());
assert!(referencing.is_empty());
assert!(trigger_object.is_none());
assert!(condition.is_none());
assert!(!statements_as);
assert!(characteristics.is_none());
}
_ => unreachable!("Expected CREATE TRIGGER statement"),
}
let statement3 = "CREATE TRIGGER cleanup_orders AFTER DELETE ON customers BEGIN DELETE FROM orders WHERE customer_id = OLD.id; DELETE FROM invoices WHERE customer_id = OLD.id; END";
match sqlite().verified_stmt(statement3) {
Statement::CreateTrigger(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table,
events,
table_name,
referenced_table_name,
referencing,
trigger_object,
condition,
exec_body: _,
statements_as,
statements: _,
characteristics,
}) => {
assert!(!or_alter);
assert!(!temporary);
assert!(!or_replace);
assert!(!is_constraint);
assert_eq!(name.to_string(), "cleanup_orders");
assert_eq!(period, TriggerPeriod::After);
assert!(period_before_table);
assert_eq!(events, vec![TriggerEvent::Delete]);
assert_eq!(table_name.to_string(), "customers");
assert!(referenced_table_name.is_none());
assert!(referencing.is_empty());
assert!(trigger_object.is_none());
assert!(condition.is_none());
assert!(!statements_as);
assert!(characteristics.is_none());
}
_ => unreachable!("Expected CREATE TRIGGER statement"),
}
let statement4 = "CREATE TRIGGER trg_before_update BEFORE UPDATE ON products FOR EACH ROW WHEN NEW.price < 0 BEGIN SELECT RAISE(ABORT, 'Price cannot be negative'); END";
match sqlite().verified_stmt(statement4) {
Statement::CreateTrigger(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table,
events,
table_name,
referenced_table_name,
referencing,
trigger_object,
condition,
exec_body: _,
statements_as,
statements: _,
characteristics,
}) => {
assert!(!or_alter);
assert!(!temporary);
assert!(!or_replace);
assert!(!is_constraint);
assert_eq!(name.to_string(), "trg_before_update");
assert_eq!(period, TriggerPeriod::Before);
assert!(period_before_table);
assert_eq!(events, vec![TriggerEvent::Update(Vec::new())]);
assert_eq!(table_name.to_string(), "products");
assert!(referenced_table_name.is_none());
assert!(referencing.is_empty());
assert_eq!(
trigger_object,
Some(TriggerObjectKind::ForEach(TriggerObject::Row))
);
assert!(condition.is_some());
assert!(!statements_as);
assert!(characteristics.is_none());
}
_ => unreachable!("Expected CREATE TRIGGER statement"),
}
// We test a INSTEAD OF trigger on a view
let statement5 = "CREATE TRIGGER trg_instead_of_insert INSTEAD OF INSERT ON my_view BEGIN INSERT INTO my_table (col1, col2) VALUES (NEW.col1, NEW.col2); END";
match sqlite().verified_stmt(statement5) {
Statement::CreateTrigger(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table,
events,
table_name,
referenced_table_name,
referencing,
trigger_object,
condition,
exec_body: _,
statements_as,
statements: _,
characteristics,
}) => {
assert!(!or_alter);
assert!(!temporary);
assert!(!or_replace);
assert!(!is_constraint);
assert_eq!(name.to_string(), "trg_instead_of_insert");
assert_eq!(period, TriggerPeriod::InsteadOf);
assert!(period_before_table);
assert_eq!(events, vec![TriggerEvent::Insert]);
assert_eq!(table_name.to_string(), "my_view");
assert!(referenced_table_name.is_none());
assert!(referencing.is_empty());
assert!(trigger_object.is_none());
assert!(condition.is_none());
assert!(!statements_as);
assert!(characteristics.is_none());
}
_ => unreachable!("Expected CREATE TRIGGER statement"),
}
// We test a temporary trigger
let statement6 = "CREATE TEMPORARY TRIGGER temp_trigger AFTER INSERT ON temp_table BEGIN UPDATE log_table SET count = count + 1; END";
match sqlite().verified_stmt(statement6) {
Statement::CreateTrigger(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table,
events,
table_name,
referenced_table_name,
referencing,
trigger_object,
condition,
exec_body: _,
statements_as,
statements: _,
characteristics,
}) => {
assert!(!or_alter);
assert!(temporary);
assert!(!or_replace);
assert!(!is_constraint);
assert_eq!(name.to_string(), "temp_trigger");
assert_eq!(period, TriggerPeriod::After);
assert!(period_before_table);
assert_eq!(events, vec![TriggerEvent::Insert]);
assert_eq!(table_name.to_string(), "temp_table");
assert!(referenced_table_name.is_none());
assert!(referencing.is_empty());
assert!(trigger_object.is_none());
assert!(condition.is_none());
assert!(!statements_as);
assert!(characteristics.is_none());
}
_ => unreachable!("Expected CREATE TRIGGER statement"),
}
}
#[test]
fn test_drop_trigger() {
let statement = "DROP TRIGGER IF EXISTS trg_inherit_asset_models";
match sqlite().verified_stmt(statement) {
Statement::DropTrigger(DropTrigger {
if_exists,
trigger_name,
table_name,
option,
}) => {
assert!(if_exists);
assert_eq!(trigger_name.to_string(), "trg_inherit_asset_models");
assert!(table_name.is_none());
assert!(option.is_none());
}
_ => unreachable!("Expected DROP TRIGGER statement"),
}
}
fn sqlite() -> TestedDialects {
TestedDialects::new(vec![Box::new(SQLiteDialect {})])
}