Skip to content

Commit 010dbd7

Browse files
Added support for SQLite triggers, including TEMP and optional FOR EACH ROW
1 parent f642dd5 commit 010dbd7

File tree

7 files changed

+373
-44
lines changed

7 files changed

+373
-44
lines changed

src/ast/ddl.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3199,6 +3199,22 @@ pub struct CreateTrigger {
31993199
///
32003200
/// [MsSql](https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql?view=sql-server-ver16#arguments)
32013201
pub or_alter: bool,
3202+
/// True if this is a temporary trigger, which is supported in SQLite.
3203+
///
3204+
/// The possible syntaxes are two:
3205+
///
3206+
/// ```sql
3207+
/// CREATE TEMP TRIGGER trigger_name
3208+
/// ```
3209+
///
3210+
/// or
3211+
///
3212+
/// ```sql
3213+
/// CREATE TEMPORARY TRIGGER trigger_name
3214+
/// ```
3215+
///
3216+
/// [Temporary Triggers in SQLite](https://sqlite.org/lang_createtrigger.html#temp_triggers_on_non_temp_tables)
3217+
pub temporary: bool,
32023218
/// The `OR REPLACE` clause is used to re-create the trigger if it already exists.
32033219
///
32043220
/// Example:
@@ -3243,14 +3259,16 @@ pub struct CreateTrigger {
32433259
/// ```
32443260
pub period: TriggerPeriod,
32453261
/// Whether the trigger period was specified before the target table name.
3262+
/// This does not refer to whether the period is BEFORE, AFTER, or INSTEAD OF,
3263+
/// but rather the position of the period clause in relation to the table name.
32463264
///
32473265
/// ```sql
3248-
/// -- period_before_table == true: Postgres, MySQL, and standard SQL
3266+
/// -- period_specified_before_table == true: Postgres, MySQL, and standard SQL
32493267
/// CREATE TRIGGER t BEFORE INSERT ON table_name ...;
3250-
/// -- period_before_table == false: MSSQL
3268+
/// -- period_specified_before_table == false: MSSQL
32513269
/// CREATE TRIGGER t ON table_name BEFORE INSERT ...;
32523270
/// ```
3253-
pub period_before_table: bool,
3271+
pub period_specified_before_table: bool,
32543272
/// Multiple events can be specified using OR, such as `INSERT`, `UPDATE`, `DELETE`, or `TRUNCATE`.
32553273
pub events: Vec<TriggerEvent>,
32563274
/// The table on which the trigger is to be created.
@@ -3262,7 +3280,9 @@ pub struct CreateTrigger {
32623280
pub referencing: Vec<TriggerReferencing>,
32633281
/// This specifies whether the trigger function should be fired once for
32643282
/// every row affected by the trigger event, or just once per SQL statement.
3265-
pub trigger_object: TriggerObject,
3283+
/// This is optional in some SQL dialects, such as SQLite, and if not specified, in
3284+
/// those cases, the implied default is `FOR EACH ROW`.
3285+
pub trigger_object: Option<TriggerObject>,
32663286
/// Whether to include the `EACH` term of the `FOR EACH`, as it is optional syntax.
32673287
pub include_each: bool,
32683288
/// Triggering conditions
@@ -3281,10 +3301,11 @@ impl Display for CreateTrigger {
32813301
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32823302
let CreateTrigger {
32833303
or_alter,
3304+
temporary,
32843305
or_replace,
32853306
is_constraint,
32863307
name,
3287-
period_before_table,
3308+
period_specified_before_table,
32883309
period,
32893310
events,
32903311
table_name,
@@ -3300,13 +3321,14 @@ impl Display for CreateTrigger {
33003321
} = self;
33013322
write!(
33023323
f,
3303-
"CREATE {or_alter}{or_replace}{is_constraint}TRIGGER {name} ",
3324+
"CREATE {temporary}{or_alter}{or_replace}{is_constraint}TRIGGER {name} ",
3325+
temporary = if *temporary { "TEMPORARY " } else { "" },
33043326
or_alter = if *or_alter { "OR ALTER " } else { "" },
33053327
or_replace = if *or_replace { "OR REPLACE " } else { "" },
33063328
is_constraint = if *is_constraint { "CONSTRAINT " } else { "" },
33073329
)?;
33083330

3309-
if *period_before_table {
3331+
if *period_specified_before_table {
33103332
write!(f, "{period}")?;
33113333
if !events.is_empty() {
33123334
write!(f, " {}", display_separated(events, " OR "))?;
@@ -3332,10 +3354,12 @@ impl Display for CreateTrigger {
33323354
write!(f, " REFERENCING {}", display_separated(referencing, " "))?;
33333355
}
33343356

3335-
if *include_each {
3336-
write!(f, " FOR EACH {trigger_object}")?;
3337-
} else if exec_body.is_some() {
3338-
write!(f, " FOR {trigger_object}")?;
3357+
if let Some(trigger_object) = trigger_object {
3358+
if *include_each {
3359+
write!(f, " FOR EACH {trigger_object}")?;
3360+
} else if exec_body.is_some() {
3361+
write!(f, " FOR {trigger_object}")?;
3362+
}
33393363
}
33403364
if let Some(condition) = condition {
33413365
write!(f, " WHEN {condition}")?;

src/dialect/mssql.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,16 +254,17 @@ impl MsSqlDialect {
254254

255255
Ok(CreateTrigger {
256256
or_alter,
257+
temporary: false,
257258
or_replace: false,
258259
is_constraint: false,
259260
name,
260261
period,
261-
period_before_table: false,
262+
period_specified_before_table: false,
262263
events,
263264
table_name,
264265
referenced_table_name: None,
265266
referencing: Vec::new(),
266-
trigger_object: TriggerObject::Statement,
267+
trigger_object: Some(TriggerObject::Statement),
267268
include_each: false,
268269
condition: None,
269270
exec_body: None,

src/parser/mod.rs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4750,9 +4750,9 @@ impl<'a> Parser<'a> {
47504750
} else if self.parse_keyword(Keyword::DOMAIN) {
47514751
self.parse_create_domain()
47524752
} else if self.parse_keyword(Keyword::TRIGGER) {
4753-
self.parse_create_trigger(or_alter, or_replace, false)
4753+
self.parse_create_trigger(temporary, or_alter, or_replace, false)
47544754
} else if self.parse_keywords(&[Keyword::CONSTRAINT, Keyword::TRIGGER]) {
4755-
self.parse_create_trigger(or_alter, or_replace, true)
4755+
self.parse_create_trigger(temporary, or_alter, or_replace, true)
47564756
} else if self.parse_keyword(Keyword::MACRO) {
47574757
self.parse_create_macro(or_replace, temporary)
47584758
} else if self.parse_keyword(Keyword::SECRET) {
@@ -5546,7 +5546,8 @@ impl<'a> Parser<'a> {
55465546
/// DROP TRIGGER [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ]
55475547
/// ```
55485548
pub fn parse_drop_trigger(&mut self) -> Result<Statement, ParserError> {
5549-
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
5549+
if !dialect_of!(self is PostgreSqlDialect | SQLiteDialect | GenericDialect | MySqlDialect | MsSqlDialect)
5550+
{
55505551
self.prev_token();
55515552
return self.expected("an object type after DROP", self.peek_token());
55525553
}
@@ -5574,11 +5575,13 @@ impl<'a> Parser<'a> {
55745575

55755576
pub fn parse_create_trigger(
55765577
&mut self,
5578+
temporary: bool,
55775579
or_alter: bool,
55785580
or_replace: bool,
55795581
is_constraint: bool,
55805582
) -> Result<Statement, ParserError> {
5581-
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
5583+
if !dialect_of!(self is PostgreSqlDialect | SQLiteDialect | GenericDialect | MySqlDialect | MsSqlDialect)
5584+
{
55825585
self.prev_token();
55835586
return self.expected("an object type after CREATE", self.peek_token());
55845587
}
@@ -5605,14 +5608,24 @@ impl<'a> Parser<'a> {
56055608
}
56065609
}
56075610

5608-
self.expect_keyword_is(Keyword::FOR)?;
5609-
let include_each = self.parse_keyword(Keyword::EACH);
5610-
let trigger_object =
5611-
match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? {
5612-
Keyword::ROW => TriggerObject::Row,
5613-
Keyword::STATEMENT => TriggerObject::Statement,
5614-
_ => unreachable!(),
5615-
};
5611+
let (include_each, trigger_object) = if self.parse_keyword(Keyword::FOR) {
5612+
(
5613+
self.parse_keyword(Keyword::EACH),
5614+
Some(
5615+
match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? {
5616+
Keyword::ROW => TriggerObject::Row,
5617+
Keyword::STATEMENT => TriggerObject::Statement,
5618+
_ => unreachable!(),
5619+
},
5620+
),
5621+
)
5622+
} else {
5623+
if !dialect_of!(self is SQLiteDialect ) {
5624+
self.expect_keyword_is(Keyword::FOR)?;
5625+
}
5626+
5627+
(false, None)
5628+
};
56165629

56175630
let condition = self
56185631
.parse_keyword(Keyword::WHEN)
@@ -5627,13 +5640,14 @@ impl<'a> Parser<'a> {
56275640
statements = Some(self.parse_conditional_statements(&[Keyword::END])?);
56285641
}
56295642

5630-
Ok(Statement::CreateTrigger(CreateTrigger {
5643+
Ok(CreateTrigger {
56315644
or_alter,
5645+
temporary,
56325646
or_replace,
56335647
is_constraint,
56345648
name,
56355649
period,
5636-
period_before_table: true,
5650+
period_specified_before_table: true,
56375651
events,
56385652
table_name,
56395653
referenced_table_name,
@@ -5645,7 +5659,8 @@ impl<'a> Parser<'a> {
56455659
statements_as: false,
56465660
statements,
56475661
characteristics,
5648-
}))
5662+
}
5663+
.into())
56495664
}
56505665

56515666
pub fn parse_trigger_period(&mut self) -> Result<TriggerPeriod, ParserError> {

tests/sqlparser_mssql.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2386,16 +2386,17 @@ fn parse_create_trigger() {
23862386
create_stmt,
23872387
Statement::CreateTrigger(CreateTrigger {
23882388
or_alter: true,
2389+
temporary: false,
23892390
or_replace: false,
23902391
is_constraint: false,
23912392
name: ObjectName::from(vec![Ident::new("reminder1")]),
23922393
period: TriggerPeriod::After,
2393-
period_before_table: false,
2394+
period_specified_before_table: false,
23942395
events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![]),],
23952396
table_name: ObjectName::from(vec![Ident::new("Sales"), Ident::new("Customer")]),
23962397
referenced_table_name: None,
23972398
referencing: vec![],
2398-
trigger_object: TriggerObject::Statement,
2399+
trigger_object: Some(TriggerObject::Statement),
23992400
include_each: false,
24002401
condition: None,
24012402
exec_body: None,

tests/sqlparser_mysql.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3924,16 +3924,17 @@ fn parse_create_trigger() {
39243924
create_stmt,
39253925
Statement::CreateTrigger(CreateTrigger {
39263926
or_alter: false,
3927+
temporary: false,
39273928
or_replace: false,
39283929
is_constraint: false,
39293930
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
39303931
period: TriggerPeriod::Before,
3931-
period_before_table: true,
3932+
period_specified_before_table: true,
39323933
events: vec![TriggerEvent::Insert],
39333934
table_name: ObjectName::from(vec![Ident::new("emp")]),
39343935
referenced_table_name: None,
39353936
referencing: vec![],
3936-
trigger_object: TriggerObject::Row,
3937+
trigger_object: Some(TriggerObject::Row),
39373938
include_each: true,
39383939
condition: None,
39393940
exec_body: Some(TriggerExecBody {

tests/sqlparser_postgres.rs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5673,16 +5673,17 @@ fn parse_create_simple_before_insert_trigger() {
56735673
let sql = "CREATE TRIGGER check_insert BEFORE INSERT ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_insert";
56745674
let expected = Statement::CreateTrigger(CreateTrigger {
56755675
or_alter: false,
5676+
temporary: false,
56765677
or_replace: false,
56775678
is_constraint: false,
56785679
name: ObjectName::from(vec![Ident::new("check_insert")]),
56795680
period: TriggerPeriod::Before,
5680-
period_before_table: true,
5681+
period_specified_before_table: true,
56815682
events: vec![TriggerEvent::Insert],
56825683
table_name: ObjectName::from(vec![Ident::new("accounts")]),
56835684
referenced_table_name: None,
56845685
referencing: vec![],
5685-
trigger_object: TriggerObject::Row,
5686+
trigger_object: Some(TriggerObject::Row),
56865687
include_each: true,
56875688
condition: None,
56885689
exec_body: Some(TriggerExecBody {
@@ -5705,16 +5706,17 @@ fn parse_create_after_update_trigger_with_condition() {
57055706
let sql = "CREATE TRIGGER check_update AFTER UPDATE ON accounts FOR EACH ROW WHEN (NEW.balance > 10000) EXECUTE FUNCTION check_account_update";
57065707
let expected = Statement::CreateTrigger(CreateTrigger {
57075708
or_alter: false,
5709+
temporary: false,
57085710
or_replace: false,
57095711
is_constraint: false,
57105712
name: ObjectName::from(vec![Ident::new("check_update")]),
57115713
period: TriggerPeriod::After,
5712-
period_before_table: true,
5714+
period_specified_before_table: true,
57135715
events: vec![TriggerEvent::Update(vec![])],
57145716
table_name: ObjectName::from(vec![Ident::new("accounts")]),
57155717
referenced_table_name: None,
57165718
referencing: vec![],
5717-
trigger_object: TriggerObject::Row,
5719+
trigger_object: Some(TriggerObject::Row),
57185720
include_each: true,
57195721
condition: Some(Expr::Nested(Box::new(Expr::BinaryOp {
57205722
left: Box::new(Expr::CompoundIdentifier(vec![
@@ -5744,16 +5746,17 @@ fn parse_create_instead_of_delete_trigger() {
57445746
let sql = "CREATE TRIGGER check_delete INSTEAD OF DELETE ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_deletes";
57455747
let expected = Statement::CreateTrigger(CreateTrigger {
57465748
or_alter: false,
5749+
temporary: false,
57475750
or_replace: false,
57485751
is_constraint: false,
57495752
name: ObjectName::from(vec![Ident::new("check_delete")]),
57505753
period: TriggerPeriod::InsteadOf,
5751-
period_before_table: true,
5754+
period_specified_before_table: true,
57525755
events: vec![TriggerEvent::Delete],
57535756
table_name: ObjectName::from(vec![Ident::new("accounts")]),
57545757
referenced_table_name: None,
57555758
referencing: vec![],
5756-
trigger_object: TriggerObject::Row,
5759+
trigger_object: Some(TriggerObject::Row),
57575760
include_each: true,
57585761
condition: None,
57595762
exec_body: Some(TriggerExecBody {
@@ -5776,11 +5779,12 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() {
57765779
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";
57775780
let expected = Statement::CreateTrigger(CreateTrigger {
57785781
or_alter: false,
5782+
temporary: false,
57795783
or_replace: false,
57805784
is_constraint: true,
57815785
name: ObjectName::from(vec![Ident::new("check_multiple_events")]),
57825786
period: TriggerPeriod::Before,
5783-
period_before_table: true,
5787+
period_specified_before_table: true,
57845788
events: vec![
57855789
TriggerEvent::Insert,
57865790
TriggerEvent::Update(vec![]),
@@ -5789,7 +5793,7 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() {
57895793
table_name: ObjectName::from(vec![Ident::new("accounts")]),
57905794
referenced_table_name: None,
57915795
referencing: vec![],
5792-
trigger_object: TriggerObject::Row,
5796+
trigger_object: Some(TriggerObject::Row),
57935797
include_each: true,
57945798
condition: None,
57955799
exec_body: Some(TriggerExecBody {
@@ -5816,11 +5820,12 @@ fn parse_create_trigger_with_referencing() {
58165820
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";
58175821
let expected = Statement::CreateTrigger(CreateTrigger {
58185822
or_alter: false,
5823+
temporary: false,
58195824
or_replace: false,
58205825
is_constraint: false,
58215826
name: ObjectName::from(vec![Ident::new("check_referencing")]),
58225827
period: TriggerPeriod::Before,
5823-
period_before_table: true,
5828+
period_specified_before_table: true,
58245829
events: vec![TriggerEvent::Insert],
58255830
table_name: ObjectName::from(vec![Ident::new("accounts")]),
58265831
referenced_table_name: None,
@@ -5836,7 +5841,7 @@ fn parse_create_trigger_with_referencing() {
58365841
transition_relation_name: ObjectName::from(vec![Ident::new("old_accounts")]),
58375842
},
58385843
],
5839-
trigger_object: TriggerObject::Row,
5844+
trigger_object: Some(TriggerObject::Row),
58405845
include_each: true,
58415846
condition: None,
58425847
exec_body: Some(TriggerExecBody {
@@ -6132,16 +6137,17 @@ fn parse_trigger_related_functions() {
61326137
create_trigger,
61336138
Statement::CreateTrigger(CreateTrigger {
61346139
or_alter: false,
6140+
temporary: false,
61356141
or_replace: false,
61366142
is_constraint: false,
61376143
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
61386144
period: TriggerPeriod::Before,
6139-
period_before_table: true,
6145+
period_specified_before_table: true,
61406146
events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![])],
61416147
table_name: ObjectName::from(vec![Ident::new("emp")]),
61426148
referenced_table_name: None,
61436149
referencing: vec![],
6144-
trigger_object: TriggerObject::Row,
6150+
trigger_object: Some(TriggerObject::Row),
61456151
include_each: true,
61466152
condition: None,
61476153
exec_body: Some(TriggerExecBody {

0 commit comments

Comments
 (0)