This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 4490c8c5 Added support for SQLite triggers (#2037)
4490c8c5 is described below
commit 4490c8c55cbe1e6b96a29fd9ba448e06320af50a
Author: Luca Cappelletti <[email protected]>
AuthorDate: Sat Oct 11 13:16:56 2025 +0200
Added support for SQLite triggers (#2037)
Co-authored-by: Ifeanyi Ubah <[email protected]>
---
src/ast/ddl.rs | 56 +++++++--
src/ast/mod.rs | 4 +-
src/dialect/mssql.rs | 6 +-
src/parser/mod.rs | 45 +++++---
tests/sqlparser_mssql.rs | 4 +-
tests/sqlparser_mysql.rs | 4 +-
tests/sqlparser_postgres.rs | 26 ++---
tests/sqlparser_sqlite.rs | 275 ++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 374 insertions(+), 46 deletions(-)
diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index aafa8e64..3294a7a8 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<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}")?;
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 0c83b320..fef8943e 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 4fcc0e4b..f1d54cd6 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 2b365e29..70f4d856 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<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> {
diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs
index 9dccce49..e11c79f0 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 9aaa35ba..e0ddecf3 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 3c2a98e1..e18bf662 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 5083ecd0..f0d6d9b7 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 {})])
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]