Skip to content

Commit a497358

Browse files
authored
Add CREATE TRIGGER support for SQL Server (#1810)
1 parent 728645f commit a497358

File tree

7 files changed

+251
-37
lines changed

7 files changed

+251
-37
lines changed

src/ast/mod.rs

+39-9
Original file line numberDiff line numberDiff line change
@@ -2380,11 +2380,16 @@ impl fmt::Display for BeginEndStatements {
23802380
end_token: AttachedToken(end_token),
23812381
} = self;
23822382

2383-
write!(f, "{begin_token} ")?;
2383+
if begin_token.token != Token::EOF {
2384+
write!(f, "{begin_token} ")?;
2385+
}
23842386
if !statements.is_empty() {
23852387
format_statement_list(f, statements)?;
23862388
}
2387-
write!(f, " {end_token}")
2389+
if end_token.token != Token::EOF {
2390+
write!(f, " {end_token}")?;
2391+
}
2392+
Ok(())
23882393
}
23892394
}
23902395

@@ -3729,7 +3734,12 @@ pub enum Statement {
37293734
/// ```
37303735
///
37313736
/// Postgres: <https://www.postgresql.org/docs/current/sql-createtrigger.html>
3737+
/// SQL Server: <https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql>
37323738
CreateTrigger {
3739+
/// True if this is a `CREATE OR ALTER TRIGGER` statement
3740+
///
3741+
/// [MsSql](https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql?view=sql-server-ver16#arguments)
3742+
or_alter: bool,
37333743
/// The `OR REPLACE` clause is used to re-create the trigger if it already exists.
37343744
///
37353745
/// Example:
@@ -3790,7 +3800,9 @@ pub enum Statement {
37903800
/// Triggering conditions
37913801
condition: Option<Expr>,
37923802
/// Execute logic block
3793-
exec_body: TriggerExecBody,
3803+
exec_body: Option<TriggerExecBody>,
3804+
/// For SQL dialects with statement(s) for a body
3805+
statements: Option<ConditionalStatements>,
37943806
/// The characteristic of the trigger, which include whether the trigger is `DEFERRABLE`, `INITIALLY DEFERRED`, or `INITIALLY IMMEDIATE`,
37953807
characteristics: Option<ConstraintCharacteristics>,
37963808
},
@@ -4587,6 +4599,7 @@ impl fmt::Display for Statement {
45874599
}
45884600
Statement::CreateFunction(create_function) => create_function.fmt(f),
45894601
Statement::CreateTrigger {
4602+
or_alter,
45904603
or_replace,
45914604
is_constraint,
45924605
name,
@@ -4599,19 +4612,30 @@ impl fmt::Display for Statement {
45994612
condition,
46004613
include_each,
46014614
exec_body,
4615+
statements,
46024616
characteristics,
46034617
} => {
46044618
write!(
46054619
f,
4606-
"CREATE {or_replace}{is_constraint}TRIGGER {name} {period}",
4620+
"CREATE {or_alter}{or_replace}{is_constraint}TRIGGER {name} ",
4621+
or_alter = if *or_alter { "OR ALTER " } else { "" },
46074622
or_replace = if *or_replace { "OR REPLACE " } else { "" },
46084623
is_constraint = if *is_constraint { "CONSTRAINT " } else { "" },
46094624
)?;
46104625

4611-
if !events.is_empty() {
4612-
write!(f, " {}", display_separated(events, " OR "))?;
4626+
if exec_body.is_some() {
4627+
write!(f, "{period}")?;
4628+
if !events.is_empty() {
4629+
write!(f, " {}", display_separated(events, " OR "))?;
4630+
}
4631+
write!(f, " ON {table_name}")?;
4632+
} else {
4633+
write!(f, "ON {table_name}")?;
4634+
write!(f, " {period}")?;
4635+
if !events.is_empty() {
4636+
write!(f, " {}", display_separated(events, ", "))?;
4637+
}
46134638
}
4614-
write!(f, " ON {table_name}")?;
46154639

46164640
if let Some(referenced_table_name) = referenced_table_name {
46174641
write!(f, " FROM {referenced_table_name}")?;
@@ -4627,13 +4651,19 @@ impl fmt::Display for Statement {
46274651

46284652
if *include_each {
46294653
write!(f, " FOR EACH {trigger_object}")?;
4630-
} else {
4654+
} else if exec_body.is_some() {
46314655
write!(f, " FOR {trigger_object}")?;
46324656
}
46334657
if let Some(condition) = condition {
46344658
write!(f, " WHEN {condition}")?;
46354659
}
4636-
write!(f, " EXECUTE {exec_body}")
4660+
if let Some(exec_body) = exec_body {
4661+
write!(f, " EXECUTE {exec_body}")?;
4662+
}
4663+
if let Some(statements) = statements {
4664+
write!(f, " AS {statements}")?;
4665+
}
4666+
Ok(())
46374667
}
46384668
Statement::DropTrigger {
46394669
if_exists,

src/ast/trigger.rs

+2
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ impl fmt::Display for TriggerEvent {
110110
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
111111
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
112112
pub enum TriggerPeriod {
113+
For,
113114
After,
114115
Before,
115116
InsteadOf,
@@ -118,6 +119,7 @@ pub enum TriggerPeriod {
118119
impl fmt::Display for TriggerPeriod {
119120
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120121
match self {
122+
TriggerPeriod::For => write!(f, "FOR"),
121123
TriggerPeriod::After => write!(f, "AFTER"),
122124
TriggerPeriod::Before => write!(f, "BEFORE"),
123125
TriggerPeriod::InsteadOf => write!(f, "INSTEAD OF"),

src/dialect/mssql.rs

+46
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use crate::ast::helpers::attached_token::AttachedToken;
1919
use crate::ast::{
2020
BeginEndStatements, ConditionalStatementBlock, ConditionalStatements, IfStatement, Statement,
21+
TriggerObject,
2122
};
2223
use crate::dialect::Dialect;
2324
use crate::keywords::{self, Keyword};
@@ -125,6 +126,15 @@ impl Dialect for MsSqlDialect {
125126
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
126127
if parser.peek_keyword(Keyword::IF) {
127128
Some(self.parse_if_stmt(parser))
129+
} else if parser.parse_keywords(&[Keyword::CREATE, Keyword::TRIGGER]) {
130+
Some(self.parse_create_trigger(parser, false))
131+
} else if parser.parse_keywords(&[
132+
Keyword::CREATE,
133+
Keyword::OR,
134+
Keyword::ALTER,
135+
Keyword::TRIGGER,
136+
]) {
137+
Some(self.parse_create_trigger(parser, true))
128138
} else {
129139
None
130140
}
@@ -215,6 +225,42 @@ impl MsSqlDialect {
215225
}))
216226
}
217227

228+
/// Parse `CREATE TRIGGER` for [MsSql]
229+
///
230+
/// [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql
231+
fn parse_create_trigger(
232+
&self,
233+
parser: &mut Parser,
234+
or_alter: bool,
235+
) -> Result<Statement, ParserError> {
236+
let name = parser.parse_object_name(false)?;
237+
parser.expect_keyword_is(Keyword::ON)?;
238+
let table_name = parser.parse_object_name(false)?;
239+
let period = parser.parse_trigger_period()?;
240+
let events = parser.parse_comma_separated(Parser::parse_trigger_event)?;
241+
242+
parser.expect_keyword_is(Keyword::AS)?;
243+
let statements = Some(parser.parse_conditional_statements(&[Keyword::END])?);
244+
245+
Ok(Statement::CreateTrigger {
246+
or_alter,
247+
or_replace: false,
248+
is_constraint: false,
249+
name,
250+
period,
251+
events,
252+
table_name,
253+
referenced_table_name: None,
254+
referencing: Vec::new(),
255+
trigger_object: TriggerObject::Statement,
256+
include_each: false,
257+
condition: None,
258+
exec_body: None,
259+
statements,
260+
characteristics: None,
261+
})
262+
}
263+
218264
/// Parse a sequence of statements, optionally separated by semicolon.
219265
///
220266
/// Stops parsing when reaching EOF or the given keyword.

src/parser/mod.rs

+30-13
Original file line numberDiff line numberDiff line change
@@ -745,26 +745,38 @@ impl<'a> Parser<'a> {
745745
}
746746
};
747747

748+
let conditional_statements = self.parse_conditional_statements(terminal_keywords)?;
749+
750+
Ok(ConditionalStatementBlock {
751+
start_token: AttachedToken(start_token),
752+
condition,
753+
then_token,
754+
conditional_statements,
755+
})
756+
}
757+
758+
/// Parse a BEGIN/END block or a sequence of statements
759+
/// This could be inside of a conditional (IF, CASE, WHILE etc.) or an object body defined optionally BEGIN/END and one or more statements.
760+
pub(crate) fn parse_conditional_statements(
761+
&mut self,
762+
terminal_keywords: &[Keyword],
763+
) -> Result<ConditionalStatements, ParserError> {
748764
let conditional_statements = if self.peek_keyword(Keyword::BEGIN) {
749765
let begin_token = self.expect_keyword(Keyword::BEGIN)?;
750766
let statements = self.parse_statement_list(terminal_keywords)?;
751767
let end_token = self.expect_keyword(Keyword::END)?;
768+
752769
ConditionalStatements::BeginEnd(BeginEndStatements {
753770
begin_token: AttachedToken(begin_token),
754771
statements,
755772
end_token: AttachedToken(end_token),
756773
})
757774
} else {
758-
let statements = self.parse_statement_list(terminal_keywords)?;
759-
ConditionalStatements::Sequence { statements }
775+
ConditionalStatements::Sequence {
776+
statements: self.parse_statement_list(terminal_keywords)?,
777+
}
760778
};
761-
762-
Ok(ConditionalStatementBlock {
763-
start_token: AttachedToken(start_token),
764-
condition,
765-
then_token,
766-
conditional_statements,
767-
})
779+
Ok(conditional_statements)
768780
}
769781

770782
/// Parse a `RAISE` statement.
@@ -4614,9 +4626,9 @@ impl<'a> Parser<'a> {
46144626
} else if self.parse_keyword(Keyword::FUNCTION) {
46154627
self.parse_create_function(or_alter, or_replace, temporary)
46164628
} else if self.parse_keyword(Keyword::TRIGGER) {
4617-
self.parse_create_trigger(or_replace, false)
4629+
self.parse_create_trigger(or_alter, or_replace, false)
46184630
} else if self.parse_keywords(&[Keyword::CONSTRAINT, Keyword::TRIGGER]) {
4619-
self.parse_create_trigger(or_replace, true)
4631+
self.parse_create_trigger(or_alter, or_replace, true)
46204632
} else if self.parse_keyword(Keyword::MACRO) {
46214633
self.parse_create_macro(or_replace, temporary)
46224634
} else if self.parse_keyword(Keyword::SECRET) {
@@ -5314,10 +5326,11 @@ impl<'a> Parser<'a> {
53145326

53155327
pub fn parse_create_trigger(
53165328
&mut self,
5329+
or_alter: bool,
53175330
or_replace: bool,
53185331
is_constraint: bool,
53195332
) -> Result<Statement, ParserError> {
5320-
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect) {
5333+
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
53215334
self.prev_token();
53225335
return self.expected("an object type after CREATE", self.peek_token());
53235336
}
@@ -5363,6 +5376,7 @@ impl<'a> Parser<'a> {
53635376
let exec_body = self.parse_trigger_exec_body()?;
53645377

53655378
Ok(Statement::CreateTrigger {
5379+
or_alter,
53665380
or_replace,
53675381
is_constraint,
53685382
name,
@@ -5374,18 +5388,21 @@ impl<'a> Parser<'a> {
53745388
trigger_object,
53755389
include_each,
53765390
condition,
5377-
exec_body,
5391+
exec_body: Some(exec_body),
5392+
statements: None,
53785393
characteristics,
53795394
})
53805395
}
53815396

53825397
pub fn parse_trigger_period(&mut self) -> Result<TriggerPeriod, ParserError> {
53835398
Ok(
53845399
match self.expect_one_of_keywords(&[
5400+
Keyword::FOR,
53855401
Keyword::BEFORE,
53865402
Keyword::AFTER,
53875403
Keyword::INSTEAD,
53885404
])? {
5405+
Keyword::FOR => TriggerPeriod::For,
53895406
Keyword::BEFORE => TriggerPeriod::Before,
53905407
Keyword::AFTER => TriggerPeriod::After,
53915408
Keyword::INSTEAD => self

0 commit comments

Comments
 (0)