From 9826f4dac498f71520227789b63635deac0adb29 Mon Sep 17 00:00:00 2001 From: Michael Ionov Date: Thu, 20 Feb 2025 21:46:23 +0200 Subject: [PATCH 1/3] feat: adjust create and drop trigger for mysql dialect --- src/ast/mod.rs | 7 ++++-- src/parser/mod.rs | 40 ++++++++++++++++------------- tests/sqlparser_mysql.rs | 50 +++++++++++++++++++++++++++++++++++++ tests/sqlparser_postgres.rs | 4 +-- 4 files changed, 79 insertions(+), 22 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index dc50871ce..c8ef09be9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3204,7 +3204,7 @@ pub enum Statement { DropTrigger { if_exists: bool, trigger_name: ObjectName, - table_name: ObjectName, + table_name: Option, /// `CASCADE` or `RESTRICT` option: Option, }, @@ -4010,7 +4010,10 @@ impl fmt::Display for Statement { if *if_exists { write!(f, " IF EXISTS")?; } - write!(f, " {trigger_name} ON {table_name}")?; + match &table_name { + Some(table_name) => write!(f, " {trigger_name} ON {table_name}")?, + None => write!(f, " {trigger_name}")?, + }; if let Some(option) = option { write!(f, " {option}")?; } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 69268bc51..8cec7a1e5 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4970,14 +4970,19 @@ 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) { + if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect) { self.prev_token(); return self.expected("an object type after DROP", self.peek_token()); } let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); let trigger_name = self.parse_object_name(false)?; - self.expect_keyword_is(Keyword::ON)?; - let table_name = self.parse_object_name(false)?; + let table_name = match dialect_of!(self is PostgreSqlDialect | GenericDialect) { + true => { + self.expect_keyword_is(Keyword::ON)?; + Some(self.parse_object_name(false)?) + } + false => None, + }; let option = self .parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) .map(|keyword| match keyword { @@ -4998,7 +5003,7 @@ impl<'a> Parser<'a> { or_replace: bool, is_constraint: bool, ) -> Result { - if !dialect_of!(self is PostgreSqlDialect | GenericDialect) { + if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect) { self.prev_token(); return self.expected("an object type after CREATE", self.peek_token()); } @@ -5061,20 +5066,19 @@ impl<'a> Parser<'a> { } pub fn parse_trigger_period(&mut self) -> Result { - Ok( - match self.expect_one_of_keywords(&[ - Keyword::BEFORE, - Keyword::AFTER, - Keyword::INSTEAD, - ])? { - Keyword::BEFORE => TriggerPeriod::Before, - Keyword::AFTER => TriggerPeriod::After, - Keyword::INSTEAD => self - .expect_keyword_is(Keyword::OF) - .map(|_| TriggerPeriod::InsteadOf)?, - _ => unreachable!(), - }, - ) + let allowed_keywords = if dialect_of!(self is MySqlDialect) { + vec![Keyword::BEFORE, Keyword::AFTER] + } else { + vec![Keyword::BEFORE, Keyword::AFTER, Keyword::INSTEAD] + }; + Ok(match self.expect_one_of_keywords(&allowed_keywords)? { + Keyword::BEFORE => TriggerPeriod::Before, + Keyword::AFTER => TriggerPeriod::After, + Keyword::INSTEAD => self + .expect_keyword_is(Keyword::OF) + .map(|_| TriggerPeriod::InsteadOf)?, + _ => unreachable!(), + }) } pub fn parse_trigger_event(&mut self) -> Result { diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 44c8350fa..5d0753664 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -3255,3 +3255,53 @@ fn parse_looks_like_single_line_comment() { "UPDATE account SET balance = balance WHERE account_id = 5752", ); } + +#[test] +fn parse_create_trigger() { + let sql_create_trigger = r#" + CREATE TRIGGER emp_stamp BEFORE INSERT ON emp + FOR EACH ROW EXECUTE FUNCTION emp_stamp(); + "#; + let create_stmt = mysql().one_statement_parses_to(sql_create_trigger, ""); + assert_eq!( + create_stmt, + Statement::CreateTrigger { + or_replace: false, + is_constraint: false, + name: ObjectName::from(vec![Ident::new("emp_stamp")]), + period: TriggerPeriod::Before, + events: vec![TriggerEvent::Insert], + table_name: ObjectName::from(vec![Ident::new("emp")]), + referenced_table_name: None, + referencing: vec![], + trigger_object: TriggerObject::Row, + include_each: true, + condition: None, + exec_body: TriggerExecBody { + exec_type: TriggerExecBodyType::Function, + func_desc: FunctionDesc { + name: ObjectName::from(vec![Ident::new("emp_stamp")]), + args: None, + } + }, + characteristics: None, + } + ); +} + +#[test] +fn parse_drop_trigger() { + let sql_drop_trigger = "DROP TRIGGER emp_stamp;"; + let drop_stmt = mysql().one_statement_parses_to(sql_drop_trigger, ""); + assert_eq!( + drop_stmt, + Statement::DropTrigger { + if_exists: false, + trigger_name: ObjectName::from(vec![Ident::new("emp_stamp")]), + table_name: None, + option: None, + } + ); + let sql_drop_trigger_invalid = "DROP TRIGGER emp_stamp on emp;"; + assert!(mysql().parse_sql_statements(sql_drop_trigger_invalid).is_err()); +} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 312ce1186..a642af6b9 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4958,7 +4958,7 @@ fn parse_drop_trigger() { Statement::DropTrigger { if_exists, trigger_name: ObjectName::from(vec![Ident::new("check_update")]), - table_name: ObjectName::from(vec![Ident::new("table_name")]), + table_name: Some(ObjectName::from(vec![Ident::new("table_name")])), option } ); @@ -5211,7 +5211,7 @@ fn parse_trigger_related_functions() { Statement::DropTrigger { if_exists: false, trigger_name: ObjectName::from(vec![Ident::new("emp_stamp")]), - table_name: ObjectName::from(vec![Ident::new("emp")]), + table_name: Some(ObjectName::from(vec![Ident::new("emp")])), option: None } ); From 2baeb21bb1cab712dc6e31f59d358f0b442d22c0 Mon Sep 17 00:00:00 2001 From: Michael Ionov Date: Thu, 20 Feb 2025 22:04:16 +0200 Subject: [PATCH 2/3] chore: format --- tests/sqlparser_mysql.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 5d0753664..152cd8155 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -3303,5 +3303,7 @@ fn parse_drop_trigger() { } ); let sql_drop_trigger_invalid = "DROP TRIGGER emp_stamp on emp;"; - assert!(mysql().parse_sql_statements(sql_drop_trigger_invalid).is_err()); + assert!(mysql() + .parse_sql_statements(sql_drop_trigger_invalid) + .is_err()); } From 20423be71746bf246b303ed9f01e74b3dc34430e Mon Sep 17 00:00:00 2001 From: Michael Ionov Date: Sat, 22 Feb 2025 09:21:40 +0200 Subject: [PATCH 3/3] chore: cleanup after cr --- src/parser/mod.rs | 37 ++++++++++++++++++------------------- tests/sqlparser_mysql.rs | 4 ---- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 8cec7a1e5..f9a8aed79 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4976,12 +4976,10 @@ impl<'a> Parser<'a> { } let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); let trigger_name = self.parse_object_name(false)?; - let table_name = match dialect_of!(self is PostgreSqlDialect | GenericDialect) { - true => { - self.expect_keyword_is(Keyword::ON)?; - Some(self.parse_object_name(false)?) - } - false => None, + let table_name = if self.parse_keyword(Keyword::ON) { + Some(self.parse_object_name(false)?) + } else { + None }; let option = self .parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) @@ -5066,19 +5064,20 @@ impl<'a> Parser<'a> { } pub fn parse_trigger_period(&mut self) -> Result { - let allowed_keywords = if dialect_of!(self is MySqlDialect) { - vec![Keyword::BEFORE, Keyword::AFTER] - } else { - vec![Keyword::BEFORE, Keyword::AFTER, Keyword::INSTEAD] - }; - Ok(match self.expect_one_of_keywords(&allowed_keywords)? { - Keyword::BEFORE => TriggerPeriod::Before, - Keyword::AFTER => TriggerPeriod::After, - Keyword::INSTEAD => self - .expect_keyword_is(Keyword::OF) - .map(|_| TriggerPeriod::InsteadOf)?, - _ => unreachable!(), - }) + Ok( + match self.expect_one_of_keywords(&[ + Keyword::BEFORE, + Keyword::AFTER, + Keyword::INSTEAD, + ])? { + Keyword::BEFORE => TriggerPeriod::Before, + Keyword::AFTER => TriggerPeriod::After, + Keyword::INSTEAD => self + .expect_keyword_is(Keyword::OF) + .map(|_| TriggerPeriod::InsteadOf)?, + _ => unreachable!(), + }, + ) } pub fn parse_trigger_event(&mut self) -> Result { diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 152cd8155..0651c75bd 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -3302,8 +3302,4 @@ fn parse_drop_trigger() { option: None, } ); - let sql_drop_trigger_invalid = "DROP TRIGGER emp_stamp on emp;"; - assert!(mysql() - .parse_sql_statements(sql_drop_trigger_invalid) - .is_err()); }