Skip to content

Commit a4b92d6

Browse files
committed
Add support for CREATE TRIGGER for SQL Server
- similar to functions & procedures, this dialect can define triggers with a multi statement block - there's no `EXECUTE` keyword here, so that means the `exec_body` used by other dialects becomes an `Option`, and our `statements` is also optional for them
1 parent 4e392f5 commit a4b92d6

File tree

6 files changed

+221
-26
lines changed

6 files changed

+221
-26
lines changed

src/ast/mod.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2344,11 +2344,16 @@ impl fmt::Display for BeginEndStatements {
23442344
end_token: AttachedToken(end_token),
23452345
} = self;
23462346

2347-
write!(f, "{begin_token} ")?;
2347+
if begin_token.token != Token::EOF {
2348+
write!(f, "{begin_token} ")?;
2349+
}
23482350
if !statements.is_empty() {
23492351
format_statement_list(f, statements)?;
23502352
}
2351-
write!(f, " {end_token}")
2353+
if end_token.token != Token::EOF {
2354+
write!(f, " {end_token}")?;
2355+
}
2356+
Ok(())
23522357
}
23532358
}
23542359

@@ -3659,6 +3664,7 @@ pub enum Statement {
36593664
/// ```
36603665
///
36613666
/// Postgres: <https://www.postgresql.org/docs/current/sql-createtrigger.html>
3667+
/// SQL Server: <https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql>
36623668
CreateTrigger {
36633669
/// The `OR REPLACE` clause is used to re-create the trigger if it already exists.
36643670
///
@@ -3720,7 +3726,9 @@ pub enum Statement {
37203726
/// Triggering conditions
37213727
condition: Option<Expr>,
37223728
/// Execute logic block
3723-
exec_body: TriggerExecBody,
3729+
exec_body: Option<TriggerExecBody>,
3730+
/// For SQL dialects with statement(s) for a body
3731+
statements: Option<BeginEndStatements>,
37243732
/// The characteristic of the trigger, which include whether the trigger is `DEFERRABLE`, `INITIALLY DEFERRED`, or `INITIALLY IMMEDIATE`,
37253733
characteristics: Option<ConstraintCharacteristics>,
37263734
},
@@ -4526,19 +4534,29 @@ impl fmt::Display for Statement {
45264534
condition,
45274535
include_each,
45284536
exec_body,
4537+
statements,
45294538
characteristics,
45304539
} => {
45314540
write!(
45324541
f,
4533-
"CREATE {or_replace}{is_constraint}TRIGGER {name} {period}",
4542+
"CREATE {or_replace}{is_constraint}TRIGGER {name} ",
45344543
or_replace = if *or_replace { "OR REPLACE " } else { "" },
45354544
is_constraint = if *is_constraint { "CONSTRAINT " } else { "" },
45364545
)?;
45374546

4538-
if !events.is_empty() {
4539-
write!(f, " {}", display_separated(events, " OR "))?;
4547+
if exec_body.is_some() {
4548+
write!(f, "{period}")?;
4549+
if !events.is_empty() {
4550+
write!(f, " {}", display_separated(events, " OR "))?;
4551+
}
4552+
write!(f, " ON {table_name}")?;
4553+
} else {
4554+
write!(f, "ON {table_name}")?;
4555+
write!(f, " {period}")?;
4556+
if !events.is_empty() {
4557+
write!(f, " {}", display_separated(events, ", "))?;
4558+
}
45404559
}
4541-
write!(f, " ON {table_name}")?;
45424560

45434561
if let Some(referenced_table_name) = referenced_table_name {
45444562
write!(f, " FROM {referenced_table_name}")?;
@@ -4554,13 +4572,19 @@ impl fmt::Display for Statement {
45544572

45554573
if *include_each {
45564574
write!(f, " FOR EACH {trigger_object}")?;
4557-
} else {
4575+
} else if exec_body.is_some() {
45584576
write!(f, " FOR {trigger_object}")?;
45594577
}
45604578
if let Some(condition) = condition {
45614579
write!(f, " WHEN {condition}")?;
45624580
}
4563-
write!(f, " EXECUTE {exec_body}")
4581+
if let Some(exec_body) = exec_body {
4582+
write!(f, " EXECUTE {exec_body}")?;
4583+
}
4584+
if let Some(statements) = statements {
4585+
write!(f, " AS {statements}")?;
4586+
}
4587+
Ok(())
45644588
}
45654589
Statement::DropTrigger {
45664590
if_exists,

src/ast/trigger.rs

Lines changed: 2 additions & 0 deletions
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/parser/mod.rs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5259,11 +5259,15 @@ impl<'a> Parser<'a> {
52595259
or_replace: bool,
52605260
is_constraint: bool,
52615261
) -> Result<Statement, ParserError> {
5262-
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect) {
5262+
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
52635263
self.prev_token();
52645264
return self.expected("an object type after CREATE", self.peek_token());
52655265
}
52665266

5267+
if dialect_of!(self is MsSqlDialect) {
5268+
return self.parse_mssql_create_trigger(or_replace, is_constraint);
5269+
}
5270+
52675271
let name = self.parse_object_name(false)?;
52685272
let period = self.parse_trigger_period()?;
52695273

@@ -5316,18 +5320,73 @@ impl<'a> Parser<'a> {
53165320
trigger_object,
53175321
include_each,
53185322
condition,
5319-
exec_body,
5323+
exec_body: Some(exec_body),
5324+
statements: None,
53205325
characteristics,
53215326
})
53225327
}
53235328

5329+
/// Parse `CREATE TRIGGER` for [MsSql]
5330+
///
5331+
/// [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql
5332+
pub fn parse_mssql_create_trigger(
5333+
&mut self,
5334+
or_replace: bool,
5335+
is_constraint: bool,
5336+
) -> Result<Statement, ParserError> {
5337+
let name = self.parse_object_name(false)?;
5338+
self.expect_keyword_is(Keyword::ON)?;
5339+
let table_name = self.parse_object_name(false)?;
5340+
let period = self.parse_trigger_period()?;
5341+
let events = self.parse_comma_separated(Parser::parse_trigger_event)?;
5342+
5343+
self.expect_keyword_is(Keyword::AS)?;
5344+
5345+
let trigger_statements_body = if self.peek_keyword(Keyword::BEGIN) {
5346+
let begin_token = self.expect_keyword(Keyword::BEGIN)?;
5347+
let statements = self.parse_statement_list(&[Keyword::END])?;
5348+
let end_token = self.expect_keyword(Keyword::END)?;
5349+
5350+
BeginEndStatements {
5351+
begin_token: AttachedToken(begin_token),
5352+
statements,
5353+
end_token: AttachedToken(end_token),
5354+
}
5355+
} else {
5356+
BeginEndStatements {
5357+
begin_token: AttachedToken::empty(),
5358+
statements: vec![self.parse_statement()?],
5359+
end_token: AttachedToken::empty(),
5360+
}
5361+
};
5362+
5363+
Ok(Statement::CreateTrigger {
5364+
or_replace,
5365+
is_constraint,
5366+
name,
5367+
period,
5368+
events,
5369+
table_name,
5370+
referenced_table_name: None,
5371+
referencing: Vec::new(),
5372+
trigger_object: TriggerObject::Statement,
5373+
include_each: false,
5374+
condition: None,
5375+
exec_body: None,
5376+
statements: Some(trigger_statements_body),
5377+
characteristics: None,
5378+
})
5379+
}
5380+
53245381
pub fn parse_trigger_period(&mut self) -> Result<TriggerPeriod, ParserError> {
53255382
Ok(
53265383
match self.expect_one_of_keywords(&[
5384+
Keyword::FOR,
53275385
Keyword::BEFORE,
53285386
Keyword::AFTER,
53295387
Keyword::INSTEAD,
53305388
])? {
5389+
Keyword::FOR => TriggerPeriod::For,
53315390
Keyword::BEFORE => TriggerPeriod::Before,
53325391
Keyword::AFTER => TriggerPeriod::After,
53335392
Keyword::INSTEAD => self
@@ -15229,6 +15288,11 @@ impl<'a> Parser<'a> {
1522915288

1523015289
/// Parse [Statement::Return]
1523115290
fn parse_return(&mut self) -> Result<Statement, ParserError> {
15291+
if let Token::Word(w) = self.peek_token().token {
15292+
if w.keyword == Keyword::END {
15293+
return Ok(Statement::Return(ReturnStatement { value: None }));
15294+
}
15295+
}
1523215296
match self.maybe_parse(|p| p.parse_expr())? {
1523315297
Some(expr) => Ok(Statement::Return(ReturnStatement {
1523415298
value: Some(ReturnStatementValue::Expr(expr)),

tests/sqlparser_mssql.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,16 @@ fn parse_create_function() {
271271
END\
272272
";
273273
let _ = ms().verified_stmt(create_or_alter_function);
274+
275+
let create_function_with_return_expression = "\
276+
CREATE FUNCTION some_scalar_udf(@foo INT, @bar VARCHAR(256)) \
277+
RETURNS INT \
278+
AS \
279+
BEGIN \
280+
RETURN CONVERT(INT, 1) + 2; \
281+
END\
282+
";
283+
let _ = ms().verified_stmt(create_function_with_return_expression);
274284
}
275285

276286
#[test]
@@ -2129,6 +2139,94 @@ fn parse_mssql_merge_with_output() {
21292139
ms_and_generic().verified_stmt(stmt);
21302140
}
21312141

2142+
#[test]
2143+
fn parse_create_trigger() {
2144+
let create_trigger = "\
2145+
CREATE TRIGGER reminder1 \
2146+
ON Sales.Customer \
2147+
AFTER INSERT, UPDATE \
2148+
AS RAISERROR('Notify Customer Relations', 16, 10);\
2149+
";
2150+
let create_stmt = ms().verified_stmt(create_trigger);
2151+
assert_eq!(
2152+
create_stmt,
2153+
Statement::CreateTrigger {
2154+
or_replace: false,
2155+
is_constraint: false,
2156+
name: ObjectName::from(vec![Ident::new("reminder1")]),
2157+
period: TriggerPeriod::After,
2158+
events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![]),],
2159+
table_name: ObjectName::from(vec![Ident::new("Sales"), Ident::new("Customer")]),
2160+
referenced_table_name: None,
2161+
referencing: vec![],
2162+
trigger_object: TriggerObject::Statement,
2163+
include_each: false,
2164+
condition: None,
2165+
exec_body: None,
2166+
statements: Some(BeginEndStatements {
2167+
begin_token: AttachedToken::empty(),
2168+
statements: vec![Statement::RaisError {
2169+
message: Box::new(Expr::Value(
2170+
(Value::SingleQuotedString("Notify Customer Relations".to_string()))
2171+
.with_empty_span()
2172+
)),
2173+
severity: Box::new(Expr::Value(
2174+
(Value::Number("16".parse().unwrap(), false)).with_empty_span()
2175+
)),
2176+
state: Box::new(Expr::Value(
2177+
(Value::Number("10".parse().unwrap(), false)).with_empty_span()
2178+
)),
2179+
arguments: vec![],
2180+
options: vec![],
2181+
}],
2182+
end_token: AttachedToken::empty(),
2183+
}),
2184+
characteristics: None,
2185+
}
2186+
);
2187+
2188+
let multi_statement_trigger = "\
2189+
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
2190+
AS \
2191+
BEGIN \
2192+
DECLARE @var INT; \
2193+
RAISERROR('Trigger fired', 10, 1); \
2194+
END\
2195+
";
2196+
let _ = ms().verified_stmt(multi_statement_trigger);
2197+
2198+
let create_trigger_with_return = "\
2199+
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
2200+
AS \
2201+
BEGIN \
2202+
RETURN; \
2203+
END\
2204+
";
2205+
let _ = ms().verified_stmt(create_trigger_with_return);
2206+
2207+
let create_trigger_with_return = "\
2208+
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
2209+
AS \
2210+
BEGIN \
2211+
RETURN; \
2212+
END\
2213+
";
2214+
let _ = ms().verified_stmt(create_trigger_with_return);
2215+
2216+
let create_trigger_with_conditional = "\
2217+
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
2218+
AS \
2219+
BEGIN \
2220+
IF 1 = 2 \
2221+
BEGIN \
2222+
RAISERROR('Trigger fired', 10, 1); \
2223+
END; \
2224+
RETURN; \
2225+
END\
2226+
";
2227+
let _ = ms().verified_stmt(create_trigger_with_conditional);
2228+
}
2229+
21322230
#[test]
21332231
fn parse_drop_trigger() {
21342232
let sql_drop_trigger = "DROP TRIGGER emp_stamp;";

tests/sqlparser_mysql.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3532,13 +3532,14 @@ fn parse_create_trigger() {
35323532
trigger_object: TriggerObject::Row,
35333533
include_each: true,
35343534
condition: None,
3535-
exec_body: TriggerExecBody {
3535+
exec_body: Some(TriggerExecBody {
35363536
exec_type: TriggerExecBodyType::Function,
35373537
func_desc: FunctionDesc {
35383538
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
35393539
args: None,
35403540
}
3541-
},
3541+
}),
3542+
statements: None,
35423543
characteristics: None,
35433544
}
35443545
);

0 commit comments

Comments
 (0)