Skip to content

Commit a2940fd

Browse files
committed
Improve MySQL CREATE TRIGGER parsing
MySQL uses a statement body similar to MSSQL (but without the `AS` keyword) instead of `EXECUTE` body style used in Postgres and standard SQL. But unlike MSSQL, MySQL puts the trigger period before the target table. We add some flags to indicate these differences and allow parsing and round tripping MySQL triggers. The main benefit is that we can now handle MySQL triggers which include `BEGIN; ... END;` compound statements.
1 parent 698154d commit a2940fd

File tree

6 files changed

+58
-15
lines changed

6 files changed

+58
-15
lines changed

src/ast/mod.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3962,6 +3962,15 @@ pub enum Statement {
39623962
/// EXECUTE FUNCTION trigger_function();
39633963
/// ```
39643964
period: TriggerPeriod,
3965+
/// Whether the trigger period was specified before the target table name.
3966+
///
3967+
/// ```sql
3968+
/// -- period_before_table == true: Postgres, MySQL, and standard SQL
3969+
/// CREATE TRIGGER t BEFORE INSERT ON table_name ...;
3970+
/// -- period_before_table == false: MSSQL
3971+
/// CREATE TRIGGER t ON table_name BEFORE INSERT ...;
3972+
/// ```
3973+
period_before_table: bool,
39653974
/// Multiple events can be specified using OR, such as `INSERT`, `UPDATE`, `DELETE`, or `TRUNCATE`.
39663975
events: Vec<TriggerEvent>,
39673976
/// The table on which the trigger is to be created.
@@ -3980,6 +3989,8 @@ pub enum Statement {
39803989
condition: Option<Expr>,
39813990
/// Execute logic block
39823991
exec_body: Option<TriggerExecBody>,
3992+
/// For MSSQL and dialects where statements are preceded by `AS`
3993+
statements_as: bool,
39833994
/// For SQL dialects with statement(s) for a body
39843995
statements: Option<ConditionalStatements>,
39853996
/// The characteristic of the trigger, which include whether the trigger is `DEFERRABLE`, `INITIALLY DEFERRED`, or `INITIALLY IMMEDIATE`,
@@ -4944,6 +4955,7 @@ impl fmt::Display for Statement {
49444955
or_replace,
49454956
is_constraint,
49464957
name,
4958+
period_before_table,
49474959
period,
49484960
events,
49494961
table_name,
@@ -4953,6 +4965,7 @@ impl fmt::Display for Statement {
49534965
condition,
49544966
include_each,
49554967
exec_body,
4968+
statements_as,
49564969
statements,
49574970
characteristics,
49584971
} => {
@@ -4964,7 +4977,7 @@ impl fmt::Display for Statement {
49644977
is_constraint = if *is_constraint { "CONSTRAINT " } else { "" },
49654978
)?;
49664979

4967-
if exec_body.is_some() {
4980+
if *period_before_table {
49684981
write!(f, "{period}")?;
49694982
if !events.is_empty() {
49704983
write!(f, " {}", display_separated(events, " OR "))?;
@@ -5002,7 +5015,10 @@ impl fmt::Display for Statement {
50025015
write!(f, " EXECUTE {exec_body}")?;
50035016
}
50045017
if let Some(statements) = statements {
5005-
write!(f, " AS {statements}")?;
5018+
if *statements_as {
5019+
write!(f, " AS")?;
5020+
}
5021+
write!(f, " {statements}")?;
50065022
}
50075023
Ok(())
50085024
}

src/dialect/mssql.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ impl MsSqlDialect {
257257
is_constraint: false,
258258
name,
259259
period,
260+
period_before_table: false,
260261
events,
261262
table_name,
262263
referenced_table_name: None,
@@ -265,6 +266,7 @@ impl MsSqlDialect {
265266
include_each: false,
266267
condition: None,
267268
exec_body: None,
269+
statements_as: true,
268270
statements,
269271
characteristics: None,
270272
})

src/parser/mod.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5593,25 +5593,31 @@ impl<'a> Parser<'a> {
55935593
.then(|| self.parse_expr())
55945594
.transpose()?;
55955595

5596-
self.expect_keyword_is(Keyword::EXECUTE)?;
5597-
5598-
let exec_body = self.parse_trigger_exec_body()?;
5596+
let mut exec_body = None;
5597+
let mut statements = None;
5598+
if self.parse_keyword(Keyword::EXECUTE) {
5599+
exec_body = Some(self.parse_trigger_exec_body()?);
5600+
} else {
5601+
statements = Some(self.parse_conditional_statements(&[Keyword::END])?);
5602+
}
55995603

56005604
Ok(Statement::CreateTrigger {
56015605
or_alter,
56025606
or_replace,
56035607
is_constraint,
56045608
name,
56055609
period,
5610+
period_before_table: true,
56065611
events,
56075612
table_name,
56085613
referenced_table_name,
56095614
referencing,
56105615
trigger_object,
56115616
include_each,
56125617
condition,
5613-
exec_body: Some(exec_body),
5614-
statements: None,
5618+
exec_body,
5619+
statements_as: false,
5620+
statements,
56155621
characteristics,
56165622
})
56175623
}
@@ -6537,7 +6543,7 @@ impl<'a> Parser<'a> {
65376543

65386544
let args = if self.consume_token(&Token::LParen) {
65396545
if self.consume_token(&Token::RParen) {
6540-
None
6546+
Some(vec![])
65416547
} else {
65426548
let args = self.parse_comma_separated(Parser::parse_function_arg)?;
65436549
self.expect_token(&Token::RParen)?;

tests/sqlparser_mssql.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2376,6 +2376,7 @@ fn parse_create_trigger() {
23762376
is_constraint: false,
23772377
name: ObjectName::from(vec![Ident::new("reminder1")]),
23782378
period: TriggerPeriod::After,
2379+
period_before_table: false,
23792380
events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![]),],
23802381
table_name: ObjectName::from(vec![Ident::new("Sales"), Ident::new("Customer")]),
23812382
referenced_table_name: None,
@@ -2384,6 +2385,7 @@ fn parse_create_trigger() {
23842385
include_each: false,
23852386
condition: None,
23862387
exec_body: None,
2388+
statements_as: true,
23872389
statements: Some(ConditionalStatements::Sequence {
23882390
statements: vec![Statement::RaisError {
23892391
message: Box::new(Expr::Value(

tests/sqlparser_mysql.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3914,11 +3914,8 @@ fn parse_looks_like_single_line_comment() {
39143914

39153915
#[test]
39163916
fn parse_create_trigger() {
3917-
let sql_create_trigger = r#"
3918-
CREATE TRIGGER emp_stamp BEFORE INSERT ON emp
3919-
FOR EACH ROW EXECUTE FUNCTION emp_stamp();
3920-
"#;
3921-
let create_stmt = mysql().one_statement_parses_to(sql_create_trigger, "");
3917+
let sql_create_trigger = r#"CREATE TRIGGER emp_stamp BEFORE INSERT ON emp FOR EACH ROW EXECUTE FUNCTION emp_stamp()"#;
3918+
let create_stmt = mysql().verified_stmt(sql_create_trigger);
39223919
assert_eq!(
39233920
create_stmt,
39243921
Statement::CreateTrigger {
@@ -3927,6 +3924,7 @@ fn parse_create_trigger() {
39273924
is_constraint: false,
39283925
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
39293926
period: TriggerPeriod::Before,
3927+
period_before_table: true,
39303928
events: vec![TriggerEvent::Insert],
39313929
table_name: ObjectName::from(vec![Ident::new("emp")]),
39323930
referenced_table_name: None,
@@ -3938,15 +3936,22 @@ fn parse_create_trigger() {
39383936
exec_type: TriggerExecBodyType::Function,
39393937
func_desc: FunctionDesc {
39403938
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
3941-
args: None,
3939+
args: Some(vec![]),
39423940
}
39433941
}),
3942+
statements_as: false,
39443943
statements: None,
39453944
characteristics: None,
39463945
}
39473946
);
39483947
}
39493948

3949+
#[test]
3950+
fn parse_create_trigger_compound_statement() {
3951+
mysql_and_generic().verified_stmt("CREATE TRIGGER mytrigger BEFORE INSERT ON mytable FOR EACH ROW BEGIN SET NEW.a = 1; SET NEW.b = 2; END");
3952+
mysql_and_generic().verified_stmt("CREATE TRIGGER tr AFTER INSERT ON t1 FOR EACH ROW BEGIN INSERT INTO t2 VALUES (NEW.id); END");
3953+
}
3954+
39503955
#[test]
39513956
fn parse_drop_trigger() {
39523957
let sql_drop_trigger = "DROP TRIGGER emp_stamp;";

tests/sqlparser_postgres.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5552,6 +5552,7 @@ fn parse_create_simple_before_insert_trigger() {
55525552
is_constraint: false,
55535553
name: ObjectName::from(vec![Ident::new("check_insert")]),
55545554
period: TriggerPeriod::Before,
5555+
period_before_table: true,
55555556
events: vec![TriggerEvent::Insert],
55565557
table_name: ObjectName::from(vec![Ident::new("accounts")]),
55575558
referenced_table_name: None,
@@ -5566,6 +5567,7 @@ fn parse_create_simple_before_insert_trigger() {
55665567
args: None,
55675568
},
55685569
}),
5570+
statements_as: false,
55695571
statements: None,
55705572
characteristics: None,
55715573
};
@@ -5582,6 +5584,7 @@ fn parse_create_after_update_trigger_with_condition() {
55825584
is_constraint: false,
55835585
name: ObjectName::from(vec![Ident::new("check_update")]),
55845586
period: TriggerPeriod::After,
5587+
period_before_table: true,
55855588
events: vec![TriggerEvent::Update(vec![])],
55865589
table_name: ObjectName::from(vec![Ident::new("accounts")]),
55875590
referenced_table_name: None,
@@ -5603,6 +5606,7 @@ fn parse_create_after_update_trigger_with_condition() {
56035606
args: None,
56045607
},
56055608
}),
5609+
statements_as: false,
56065610
statements: None,
56075611
characteristics: None,
56085612
};
@@ -5619,6 +5623,7 @@ fn parse_create_instead_of_delete_trigger() {
56195623
is_constraint: false,
56205624
name: ObjectName::from(vec![Ident::new("check_delete")]),
56215625
period: TriggerPeriod::InsteadOf,
5626+
period_before_table: true,
56225627
events: vec![TriggerEvent::Delete],
56235628
table_name: ObjectName::from(vec![Ident::new("accounts")]),
56245629
referenced_table_name: None,
@@ -5633,6 +5638,7 @@ fn parse_create_instead_of_delete_trigger() {
56335638
args: None,
56345639
},
56355640
}),
5641+
statements_as: false,
56365642
statements: None,
56375643
characteristics: None,
56385644
};
@@ -5649,6 +5655,7 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() {
56495655
is_constraint: true,
56505656
name: ObjectName::from(vec![Ident::new("check_multiple_events")]),
56515657
period: TriggerPeriod::Before,
5658+
period_before_table: true,
56525659
events: vec![
56535660
TriggerEvent::Insert,
56545661
TriggerEvent::Update(vec![]),
@@ -5667,6 +5674,7 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() {
56675674
args: None,
56685675
},
56695676
}),
5677+
statements_as: false,
56705678
statements: None,
56715679
characteristics: Some(ConstraintCharacteristics {
56725680
deferrable: Some(true),
@@ -5687,6 +5695,7 @@ fn parse_create_trigger_with_referencing() {
56875695
is_constraint: false,
56885696
name: ObjectName::from(vec![Ident::new("check_referencing")]),
56895697
period: TriggerPeriod::Before,
5698+
period_before_table: true,
56905699
events: vec![TriggerEvent::Insert],
56915700
table_name: ObjectName::from(vec![Ident::new("accounts")]),
56925701
referenced_table_name: None,
@@ -5712,6 +5721,7 @@ fn parse_create_trigger_with_referencing() {
57125721
args: None,
57135722
},
57145723
}),
5724+
statements_as: false,
57155725
statements: None,
57165726
characteristics: None,
57175727
};
@@ -5994,6 +6004,7 @@ fn parse_trigger_related_functions() {
59946004
is_constraint: false,
59956005
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
59966006
period: TriggerPeriod::Before,
6007+
period_before_table: true,
59976008
events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![])],
59986009
table_name: ObjectName::from(vec![Ident::new("emp")]),
59996010
referenced_table_name: None,
@@ -6005,9 +6016,10 @@ fn parse_trigger_related_functions() {
60056016
exec_type: TriggerExecBodyType::Function,
60066017
func_desc: FunctionDesc {
60076018
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
6008-
args: None,
6019+
args: Some(vec![]),
60096020
}
60106021
}),
6022+
statements_as: false,
60116023
statements: None,
60126024
characteristics: None
60136025
}

0 commit comments

Comments
 (0)