diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index aafa8e644..3294a7a81 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -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, /// 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, /// Triggering conditions pub condition: Option, /// 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}")?; diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 0c83b3203..fef8943ef 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -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}; diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs index 4fcc0e4b6..f1d54cd67 100644 --- a/src/dialect/mssql.rs +++ b/src/dialect/mssql.rs @@ -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, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2b365e297..70f4d8568 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -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 { - 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 { - 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 { diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 9dccce491..e11c79f01 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -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, diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 9aaa35ba0..e0ddecf32 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -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, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 3c2a98e1a..e18bf662a 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -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, diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index 5083ecd08..f0d6d9b72 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -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 {})]) }