Skip to content

Commit e41e401

Browse files
LucaCappelletti94iffyio
authored andcommitted
Added support for SQLite triggers (apache#2037)
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
1 parent e849099 commit e41e401

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
@@ -4784,9 +4784,9 @@ impl<'a> Parser<'a> {
47844784
} else if self.parse_keyword(Keyword::DOMAIN) {
47854785
self.parse_create_domain()
47864786
} else if self.parse_keyword(Keyword::TRIGGER) {
4787-
self.parse_create_trigger(or_alter, or_replace, false)
4787+
self.parse_create_trigger(temporary, or_alter, or_replace, false)
47884788
} else if self.parse_keywords(&[Keyword::CONSTRAINT, Keyword::TRIGGER]) {
4789-
self.parse_create_trigger(or_alter, or_replace, true)
4789+
self.parse_create_trigger(temporary, or_alter, or_replace, true)
47904790
} else if self.parse_keyword(Keyword::MACRO) {
47914791
self.parse_create_macro(or_replace, temporary)
47924792
} else if self.parse_keyword(Keyword::SECRET) {
@@ -5582,7 +5582,8 @@ impl<'a> Parser<'a> {
55825582
/// DROP TRIGGER [ IF EXISTS ] name ON table_name [ CASCADE | RESTRICT ]
55835583
/// ```
55845584
pub fn parse_drop_trigger(&mut self) -> Result<Statement, ParserError> {
5585-
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
5585+
if !dialect_of!(self is PostgreSqlDialect | SQLiteDialect | GenericDialect | MySqlDialect | MsSqlDialect)
5586+
{
55865587
self.prev_token();
55875588
return self.expected("an object type after DROP", self.peek_token());
55885589
}
@@ -5610,11 +5611,13 @@ impl<'a> Parser<'a> {
56105611

56115612
pub fn parse_create_trigger(
56125613
&mut self,
5614+
temporary: bool,
56135615
or_alter: bool,
56145616
or_replace: bool,
56155617
is_constraint: bool,
56165618
) -> Result<Statement, ParserError> {
5617-
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
5619+
if !dialect_of!(self is PostgreSqlDialect | SQLiteDialect | GenericDialect | MySqlDialect | MsSqlDialect)
5620+
{
56185621
self.prev_token();
56195622
return self.expected("an object type after CREATE", self.peek_token());
56205623
}
@@ -5641,14 +5644,25 @@ impl<'a> Parser<'a> {
56415644
}
56425645
}
56435646

5644-
self.expect_keyword_is(Keyword::FOR)?;
5645-
let include_each = self.parse_keyword(Keyword::EACH);
5646-
let trigger_object =
5647-
match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? {
5648-
Keyword::ROW => TriggerObject::Row,
5649-
Keyword::STATEMENT => TriggerObject::Statement,
5650-
_ => unreachable!(),
5651-
};
5647+
let trigger_object = if self.parse_keyword(Keyword::FOR) {
5648+
let include_each = self.parse_keyword(Keyword::EACH);
5649+
let trigger_object =
5650+
match self.expect_one_of_keywords(&[Keyword::ROW, Keyword::STATEMENT])? {
5651+
Keyword::ROW => TriggerObject::Row,
5652+
Keyword::STATEMENT => TriggerObject::Statement,
5653+
_ => unreachable!(),
5654+
};
5655+
5656+
Some(if include_each {
5657+
TriggerObjectKind::ForEach(trigger_object)
5658+
} else {
5659+
TriggerObjectKind::For(trigger_object)
5660+
})
5661+
} else {
5662+
let _ = self.parse_keyword(Keyword::FOR);
5663+
5664+
None
5665+
};
56525666

56535667
let condition = self
56545668
.parse_keyword(Keyword::WHEN)
@@ -5663,8 +5677,9 @@ impl<'a> Parser<'a> {
56635677
statements = Some(self.parse_conditional_statements(&[Keyword::END])?);
56645678
}
56655679

5666-
Ok(Statement::CreateTrigger(CreateTrigger {
5680+
Ok(CreateTrigger {
56675681
or_alter,
5682+
temporary,
56685683
or_replace,
56695684
is_constraint,
56705685
name,
@@ -5675,13 +5690,13 @@ impl<'a> Parser<'a> {
56755690
referenced_table_name,
56765691
referencing,
56775692
trigger_object,
5678-
include_each,
56795693
condition,
56805694
exec_body,
56815695
statements_as: false,
56825696
statements,
56835697
characteristics,
5684-
}))
5698+
}
5699+
.into())
56855700
}
56865701

56875702
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)