Skip to content

Commit 4490c8c

Browse files
Added support for SQLite triggers (#2037)
Co-authored-by: Ifeanyi Ubah <[email protected]>
1 parent cc595cf commit 4490c8c

File tree

8 files changed

+374
-46
lines changed

8 files changed

+374
-46
lines changed

src/ast/ddl.rs

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2922,6 +2922,26 @@ impl Spanned for RenameTableNameKind {
29222922
}
29232923
}
29242924

2925+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2926+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2927+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
2928+
/// Whether the syntax used for the trigger object (ROW or STATEMENT) is `FOR` or `FOR EACH`.
2929+
pub enum TriggerObjectKind {
2930+
/// The `FOR` syntax is used.
2931+
For(TriggerObject),
2932+
/// The `FOR EACH` syntax is used.
2933+
ForEach(TriggerObject),
2934+
}
2935+
2936+
impl Display for TriggerObjectKind {
2937+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2938+
match self {
2939+
TriggerObjectKind::For(obj) => write!(f, "FOR {obj}"),
2940+
TriggerObjectKind::ForEach(obj) => write!(f, "FOR EACH {obj}"),
2941+
}
2942+
}
2943+
}
2944+
29252945
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
29262946
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
29272947
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -2943,6 +2963,23 @@ pub struct CreateTrigger {
29432963
///
29442964
/// [MsSql](https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql?view=sql-server-ver16#arguments)
29452965
pub or_alter: bool,
2966+
/// True if this is a temporary trigger.
2967+
///
2968+
/// Examples:
2969+
///
2970+
/// ```sql
2971+
/// CREATE TEMP TRIGGER trigger_name
2972+
/// ```
2973+
///
2974+
/// or
2975+
///
2976+
/// ```sql
2977+
/// CREATE TEMPORARY TRIGGER trigger_name;
2978+
/// CREATE TEMP TRIGGER trigger_name;
2979+
/// ```
2980+
///
2981+
/// [SQLite](https://sqlite.org/lang_createtrigger.html#temp_triggers_on_non_temp_tables)
2982+
pub temporary: bool,
29462983
/// The `OR REPLACE` clause is used to re-create the trigger if it already exists.
29472984
///
29482985
/// Example:
@@ -2987,6 +3024,8 @@ pub struct CreateTrigger {
29873024
/// ```
29883025
pub period: TriggerPeriod,
29893026
/// Whether the trigger period was specified before the target table name.
3027+
/// This does not refer to whether the period is BEFORE, AFTER, or INSTEAD OF,
3028+
/// but rather the position of the period clause in relation to the table name.
29903029
///
29913030
/// ```sql
29923031
/// -- period_before_table == true: Postgres, MySQL, and standard SQL
@@ -3006,9 +3045,9 @@ pub struct CreateTrigger {
30063045
pub referencing: Vec<TriggerReferencing>,
30073046
/// This specifies whether the trigger function should be fired once for
30083047
/// every row affected by the trigger event, or just once per SQL statement.
3009-
pub trigger_object: TriggerObject,
3010-
/// Whether to include the `EACH` term of the `FOR EACH`, as it is optional syntax.
3011-
pub include_each: bool,
3048+
/// This is optional in some SQL dialects, such as SQLite, and if not specified, in
3049+
/// those cases, the implied default is `FOR EACH ROW`.
3050+
pub trigger_object: Option<TriggerObjectKind>,
30123051
/// Triggering conditions
30133052
pub condition: Option<Expr>,
30143053
/// Execute logic block
@@ -3025,6 +3064,7 @@ impl Display for CreateTrigger {
30253064
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30263065
let CreateTrigger {
30273066
or_alter,
3067+
temporary,
30283068
or_replace,
30293069
is_constraint,
30303070
name,
@@ -3036,15 +3076,15 @@ impl Display for CreateTrigger {
30363076
referencing,
30373077
trigger_object,
30383078
condition,
3039-
include_each,
30403079
exec_body,
30413080
statements_as,
30423081
statements,
30433082
characteristics,
30443083
} = self;
30453084
write!(
30463085
f,
3047-
"CREATE {or_alter}{or_replace}{is_constraint}TRIGGER {name} ",
3086+
"CREATE {temporary}{or_alter}{or_replace}{is_constraint}TRIGGER {name} ",
3087+
temporary = if *temporary { "TEMPORARY " } else { "" },
30483088
or_alter = if *or_alter { "OR ALTER " } else { "" },
30493089
or_replace = if *or_replace { "OR REPLACE " } else { "" },
30503090
is_constraint = if *is_constraint { "CONSTRAINT " } else { "" },
@@ -3076,10 +3116,8 @@ impl Display for CreateTrigger {
30763116
write!(f, " REFERENCING {}", display_separated(referencing, " "))?;
30773117
}
30783118

3079-
if *include_each {
3080-
write!(f, " FOR EACH {trigger_object}")?;
3081-
} else if exec_body.is_some() {
3082-
write!(f, " FOR {trigger_object}")?;
3119+
if let Some(trigger_object) = trigger_object {
3120+
write!(f, " {trigger_object}")?;
30833121
}
30843122
if let Some(condition) = condition {
30853123
write!(f, " WHEN {condition}")?;

src/ast/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ pub use self::ddl::{
7070
IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
7171
IdentityPropertyOrder, IndexColumn, IndexOption, IndexType, KeyOrIndexDisplay, Msck,
7272
NullsDistinctOption, Owner, Partition, ProcedureParam, ReferentialAction, RenameTableNameKind,
73-
ReplicaIdentity, TagsColumnOption, Truncate, UserDefinedTypeCompositeAttributeDef,
74-
UserDefinedTypeRepresentation, ViewColumnDef,
73+
ReplicaIdentity, TagsColumnOption, TriggerObjectKind, Truncate,
74+
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef,
7575
};
7676
pub use self::dml::{Delete, Insert, Update};
7777
pub use self::operator::{BinaryOperator, UnaryOperator};

src/dialect/mssql.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
use crate::ast::helpers::attached_token::AttachedToken;
1919
use crate::ast::{
2020
BeginEndStatements, ConditionalStatementBlock, ConditionalStatements, CreateTrigger,
21-
GranteesType, IfStatement, Statement, TriggerObject,
21+
GranteesType, IfStatement, Statement,
2222
};
2323
use crate::dialect::Dialect;
2424
use crate::keywords::{self, Keyword};
@@ -254,6 +254,7 @@ impl MsSqlDialect {
254254

255255
Ok(CreateTrigger {
256256
or_alter,
257+
temporary: false,
257258
or_replace: false,
258259
is_constraint: false,
259260
name,
@@ -263,8 +264,7 @@ impl MsSqlDialect {
263264
table_name,
264265
referenced_table_name: None,
265266
referencing: Vec::new(),
266-
trigger_object: TriggerObject::Statement,
267-
include_each: false,
267+
trigger_object: None,
268268
condition: None,
269269
exec_body: None,
270270
statements_as: true,

src/parser/mod.rs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4753,9 +4753,9 @@ impl<'a> Parser<'a> {
47534753
} else if self.parse_keyword(Keyword::DOMAIN) {
47544754
self.parse_create_domain()
47554755
} else if self.parse_keyword(Keyword::TRIGGER) {
4756-
self.parse_create_trigger(or_alter, or_replace, false)
4756+
self.parse_create_trigger(temporary, or_alter, or_replace, false)
47574757
} else if self.parse_keywords(&[Keyword::CONSTRAINT, Keyword::TRIGGER]) {
4758-
self.parse_create_trigger(or_alter, or_replace, true)
4758+
self.parse_create_trigger(temporary, or_alter, or_replace, true)
47594759
} else if self.parse_keyword(Keyword::MACRO) {
47604760
self.parse_create_macro(or_replace, temporary)
47614761
} else if self.parse_keyword(Keyword::SECRET) {
@@ -5551,7 +5551,8 @@ impl<'a> Parser<'a> {
55515551
/// DROP TRIGGER [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ]
55525552
/// ```
55535553
pub fn parse_drop_trigger(&mut self) -> Result<Statement, ParserError> {
5554-
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
5554+
if !dialect_of!(self is PostgreSqlDialect | SQLiteDialect | GenericDialect | MySqlDialect | MsSqlDialect)
5555+
{
55555556
self.prev_token();
55565557
return self.expected("an object type after DROP", self.peek_token());
55575558
}
@@ -5579,11 +5580,13 @@ impl<'a> Parser<'a> {
55795580

55805581
pub fn parse_create_trigger(
55815582
&mut self,
5583+
temporary: bool,
55825584
or_alter: bool,
55835585
or_replace: bool,
55845586
is_constraint: bool,
55855587
) -> Result<Statement, ParserError> {
5586-
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
5588+
if !dialect_of!(self is PostgreSqlDialect | SQLiteDialect | GenericDialect | MySqlDialect | MsSqlDialect)
5589+
{
55875590
self.prev_token();
55885591
return self.expected("an object type after CREATE", self.peek_token());
55895592
}
@@ -5610,14 +5613,25 @@ impl<'a> Parser<'a> {
56105613
}
56115614
}
56125615

5613-
self.expect_keyword_is(Keyword::FOR)?;
5614-
let include_each = self.parse_keyword(Keyword::EACH);
5615-
let trigger_object =
5616-
match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? {
5617-
Keyword::ROW => TriggerObject::Row,
5618-
Keyword::STATEMENT => TriggerObject::Statement,
5619-
_ => unreachable!(),
5620-
};
5616+
let trigger_object = if self.parse_keyword(Keyword::FOR) {
5617+
let include_each = self.parse_keyword(Keyword::EACH);
5618+
let trigger_object =
5619+
match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? {
5620+
Keyword::ROW => TriggerObject::Row,
5621+
Keyword::STATEMENT => TriggerObject::Statement,
5622+
_ => unreachable!(),
5623+
};
5624+
5625+
Some(if include_each {
5626+
TriggerObjectKind::ForEach(trigger_object)
5627+
} else {
5628+
TriggerObjectKind::For(trigger_object)
5629+
})
5630+
} else {
5631+
let _ = self.parse_keyword(Keyword::FOR);
5632+
5633+
None
5634+
};
56215635

56225636
let condition = self
56235637
.parse_keyword(Keyword::WHEN)
@@ -5632,8 +5646,9 @@ impl<'a> Parser<'a> {
56325646
statements = Some(self.parse_conditional_statements(&[Keyword::END])?);
56335647
}
56345648

5635-
Ok(Statement::CreateTrigger(CreateTrigger {
5649+
Ok(CreateTrigger {
56365650
or_alter,
5651+
temporary,
56375652
or_replace,
56385653
is_constraint,
56395654
name,
@@ -5644,13 +5659,13 @@ impl<'a> Parser<'a> {
56445659
referenced_table_name,
56455660
referencing,
56465661
trigger_object,
5647-
include_each,
56485662
condition,
56495663
exec_body,
56505664
statements_as: false,
56515665
statements,
56525666
characteristics,
5653-
}))
5667+
}
5668+
.into())
56545669
}
56555670

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

tests/sqlparser_mssql.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2388,6 +2388,7 @@ fn parse_create_trigger() {
23882388
create_stmt,
23892389
Statement::CreateTrigger(CreateTrigger {
23902390
or_alter: true,
2391+
temporary: false,
23912392
or_replace: false,
23922393
is_constraint: false,
23932394
name: ObjectName::from(vec![Ident::new("reminder1")]),
@@ -2397,8 +2398,7 @@ fn parse_create_trigger() {
23972398
table_name: ObjectName::from(vec![Ident::new("Sales"), Ident::new("Customer")]),
23982399
referenced_table_name: None,
23992400
referencing: vec![],
2400-
trigger_object: TriggerObject::Statement,
2401-
include_each: false,
2401+
trigger_object: None,
24022402
condition: None,
24032403
exec_body: None,
24042404
statements_as: true,

tests/sqlparser_mysql.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4018,6 +4018,7 @@ fn parse_create_trigger() {
40184018
create_stmt,
40194019
Statement::CreateTrigger(CreateTrigger {
40204020
or_alter: false,
4021+
temporary: false,
40214022
or_replace: false,
40224023
is_constraint: false,
40234024
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
@@ -4027,8 +4028,7 @@ fn parse_create_trigger() {
40274028
table_name: ObjectName::from(vec![Ident::new("emp")]),
40284029
referenced_table_name: None,
40294030
referencing: vec![],
4030-
trigger_object: TriggerObject::Row,
4031-
include_each: true,
4031+
trigger_object: Some(TriggerObjectKind::ForEach(TriggerObject::Row)),
40324032
condition: None,
40334033
exec_body: Some(TriggerExecBody {
40344034
exec_type: TriggerExecBodyType::Function,

0 commit comments

Comments
 (0)