Skip to content

Commit 761cb24

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`
1 parent 618eb4d commit 761cb24

File tree

6 files changed

+228
-23
lines changed

6 files changed

+228
-23
lines changed

src/ast/mod.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3624,6 +3624,7 @@ pub enum Statement {
36243624
/// ```
36253625
///
36263626
/// Postgres: <https://www.postgresql.org/docs/current/sql-createtrigger.html>
3627+
/// SQL Server: <https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql>
36273628
CreateTrigger {
36283629
/// The `OR REPLACE` clause is used to re-create the trigger if it already exists.
36293630
///
@@ -3685,7 +3686,9 @@ pub enum Statement {
36853686
/// Triggering conditions
36863687
condition: Option<Expr>,
36873688
/// Execute logic block
3688-
exec_body: TriggerExecBody,
3689+
exec_body: Option<TriggerExecBody>,
3690+
/// For SQL dialects with statement(s) for a body
3691+
statements: Vec<Statement>,
36893692
/// The characteristic of the trigger, which include whether the trigger is `DEFERRABLE`, `INITIALLY DEFERRED`, or `INITIALLY IMMEDIATE`,
36903693
characteristics: Option<ConstraintCharacteristics>,
36913694
},
@@ -4057,6 +4060,11 @@ pub enum Statement {
40574060
/// RETURN scalar_expression
40584061
///
40594062
/// See: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql
4063+
///
4064+
/// for Triggers:
4065+
/// RETURN
4066+
///
4067+
/// See: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql
40604068
Return(ReturnStatement),
40614069
}
40624070

@@ -4486,6 +4494,7 @@ impl fmt::Display for Statement {
44864494
condition,
44874495
include_each,
44884496
exec_body,
4497+
statements,
44894498
characteristics,
44904499
} => {
44914500
write!(
@@ -4520,7 +4529,20 @@ impl fmt::Display for Statement {
45204529
if let Some(condition) = condition {
45214530
write!(f, " WHEN {condition}")?;
45224531
}
4523-
write!(f, " EXECUTE {exec_body}")
4532+
if let Some(exec_body) = exec_body {
4533+
write!(f, " EXECUTE {exec_body}")?;
4534+
}
4535+
if !statements.is_empty() {
4536+
write!(f, " AS ")?;
4537+
if statements.len() > 1 {
4538+
write!(f, "BEGIN ")?;
4539+
}
4540+
write!(f, "{}", display_separated(statements, "; "))?;
4541+
if statements.len() > 1 {
4542+
write!(f, " END")?;
4543+
}
4544+
}
4545+
Ok(())
45244546
}
45254547
Statement::DropTrigger {
45264548
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: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5266,11 +5266,15 @@ impl<'a> Parser<'a> {
52665266
or_replace: bool,
52675267
is_constraint: bool,
52685268
) -> Result<Statement, ParserError> {
5269-
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect) {
5269+
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
52705270
self.prev_token();
52715271
return self.expected("an object type after CREATE", self.peek_token());
52725272
}
52735273

5274+
if dialect_of!(self is MsSqlDialect) {
5275+
return self.parse_mssql_create_trigger(or_replace, is_constraint);
5276+
}
5277+
52745278
let name = self.parse_object_name(false)?;
52755279
let period = self.parse_trigger_period()?;
52765280

@@ -5323,18 +5327,69 @@ impl<'a> Parser<'a> {
53235327
trigger_object,
53245328
include_each,
53255329
condition,
5326-
exec_body,
5330+
exec_body: Some(exec_body),
5331+
statements: vec![],
53275332
characteristics,
53285333
})
53295334
}
53305335

5336+
/// Parse `CREATE TRIGGER` for [Mssql]
5337+
///
5338+
/// [Mssql]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql
5339+
pub fn parse_mssql_create_trigger(
5340+
&mut self,
5341+
or_replace: bool,
5342+
is_constraint: bool,
5343+
) -> Result<Statement, ParserError> {
5344+
let name = self.parse_object_name(false)?;
5345+
self.expect_keyword_is(Keyword::ON)?;
5346+
let table_name = self.parse_object_name(false)?;
5347+
let period = self.parse_trigger_period()?;
5348+
let events = self.parse_comma_separated(Parser::parse_trigger_event)?;
5349+
5350+
self.expect_keyword_is(Keyword::AS)?;
5351+
5352+
let statements = if self.peek_keyword(Keyword::BEGIN) {
5353+
self.next_token();
5354+
let mut result = self.parse_statements()?;
5355+
// note: `parse_statements` will consume the `END` token & produce a Commit statement...
5356+
if let Some(Statement::Commit{ chain, end, modifier }) = result.last() {
5357+
if *chain == false && *end == true && *modifier == None {
5358+
result = result[..result.len() - 1].to_vec();
5359+
}
5360+
}
5361+
result
5362+
} else {
5363+
vec![self.parse_statement()?]
5364+
};
5365+
5366+
Ok(Statement::CreateTrigger {
5367+
or_replace,
5368+
is_constraint,
5369+
name,
5370+
period,
5371+
events,
5372+
table_name,
5373+
referenced_table_name: None,
5374+
referencing: Vec::new(),
5375+
trigger_object: TriggerObject::Statement,
5376+
include_each: false,
5377+
condition: None,
5378+
exec_body: None,
5379+
statements,
5380+
characteristics: None,
5381+
})
5382+
}
5383+
53315384
pub fn parse_trigger_period(&mut self) -> Result<TriggerPeriod, ParserError> {
53325385
Ok(
53335386
match self.expect_one_of_keywords(&[
5387+
Keyword::FOR,
53345388
Keyword::BEFORE,
53355389
Keyword::AFTER,
53365390
Keyword::INSTEAD,
53375391
])? {
5392+
Keyword::FOR => TriggerPeriod::For,
53385393
Keyword::BEFORE => TriggerPeriod::Before,
53395394
Keyword::AFTER => TriggerPeriod::After,
53405395
Keyword::INSTEAD => self
@@ -15076,10 +15131,14 @@ impl<'a> Parser<'a> {
1507615131
}
1507715132

1507815133
fn parse_return(&mut self) -> Result<Statement, ParserError> {
15079-
let expr = self.parse_expr()?;
15080-
Ok(Statement::Return(ReturnStatement {
15081-
value: Some(ReturnStatementValue::Expr(expr)),
15082-
}))
15134+
match self.maybe_parse(|p| p.parse_expr())? {
15135+
Some(expr) => Ok(Statement::Return(ReturnStatement {
15136+
value: Some(ReturnStatementValue::Expr(expr)),
15137+
})),
15138+
None => Ok(Statement::Return(ReturnStatement {
15139+
value: None,
15140+
}))
15141+
}
1508315142
}
1508415143

1508515144
/// Consume the parser and return its underlying token buffer

tests/sqlparser_mssql.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2263,3 +2263,118 @@ fn parse_mssql_merge_with_output() {
22632263
OUTPUT $action, deleted.ProductID INTO dsi.temp_products";
22642264
ms_and_generic().verified_stmt(stmt);
22652265
}
2266+
2267+
#[test]
2268+
fn parse_create_trigger() {
2269+
let create_trigger = r#"
2270+
CREATE TRIGGER reminder1
2271+
ON Sales.Customer
2272+
AFTER INSERT, UPDATE
2273+
AS RAISERROR ('Notify Customer Relations', 16, 10);
2274+
"#;
2275+
let create_stmt = ms().one_statement_parses_to(create_trigger, "");
2276+
assert_eq!(
2277+
create_stmt,
2278+
Statement::CreateTrigger {
2279+
or_replace: false,
2280+
is_constraint: false,
2281+
name: ObjectName::from(vec![Ident::new("reminder1")]),
2282+
period: TriggerPeriod::After,
2283+
events: vec![
2284+
TriggerEvent::Insert,
2285+
TriggerEvent::Update(vec![]),
2286+
],
2287+
table_name: ObjectName::from(vec![Ident::new("Sales"), Ident::new("Customer")]),
2288+
referenced_table_name: None,
2289+
referencing: vec![],
2290+
trigger_object: TriggerObject::Statement,
2291+
include_each: false,
2292+
condition: None,
2293+
exec_body: None,
2294+
statements: vec![Statement::RaisError {
2295+
message: Box::new(Expr::Value(
2296+
(Value::SingleQuotedString("Notify Customer Relations".to_string())).with_empty_span()
2297+
)),
2298+
severity: Box::new(Expr::Value(
2299+
(Value::Number("16".parse().unwrap(), false)).with_empty_span()
2300+
)),
2301+
state: Box::new(Expr::Value(
2302+
(Value::Number("10".parse().unwrap(), false)).with_empty_span()
2303+
)),
2304+
arguments: vec![],
2305+
options: vec![],
2306+
}],
2307+
characteristics: None,
2308+
}
2309+
);
2310+
2311+
let multi_statement_trigger = r#"
2312+
CREATE TRIGGER some_trigger ON some_table FOR INSERT
2313+
AS
2314+
BEGIN
2315+
RAISERROR('Trigger fired', 10, 1);
2316+
END
2317+
"#;
2318+
let create_stmt = ms().one_statement_parses_to(multi_statement_trigger, "");
2319+
assert_eq!(
2320+
create_stmt,
2321+
Statement::CreateTrigger {
2322+
or_replace: false,
2323+
is_constraint: false,
2324+
name: ObjectName::from(vec![Ident::new("some_trigger")]),
2325+
period: TriggerPeriod::For,
2326+
events: vec![TriggerEvent::Insert],
2327+
table_name: ObjectName::from(vec![Ident::new("some_table")]),
2328+
referenced_table_name: None,
2329+
referencing: vec![],
2330+
trigger_object: TriggerObject::Statement,
2331+
include_each: false,
2332+
condition: None,
2333+
exec_body: None,
2334+
statements: vec![Statement::RaisError {
2335+
message: Box::new(Expr::Value(
2336+
(Value::SingleQuotedString("Trigger fired".to_string())).with_empty_span()
2337+
)),
2338+
severity: Box::new(Expr::Value(
2339+
(Value::Number("10".parse().unwrap(), false)).with_empty_span()
2340+
)),
2341+
state: Box::new(Expr::Value(
2342+
(Value::Number("1".parse().unwrap(), false)).with_empty_span()
2343+
)),
2344+
arguments: vec![],
2345+
options: vec![],
2346+
}],
2347+
characteristics: None,
2348+
}
2349+
);
2350+
2351+
let create_trigger_with_return = r#"
2352+
CREATE TRIGGER some_trigger ON some_table FOR INSERT
2353+
AS
2354+
BEGIN
2355+
RETURN;
2356+
END
2357+
"#;
2358+
let create_stmt = ms().one_statement_parses_to(create_trigger_with_return, "");
2359+
assert_eq!(
2360+
create_stmt,
2361+
Statement::CreateTrigger {
2362+
or_replace: false,
2363+
is_constraint: false,
2364+
name: ObjectName::from(vec![Ident::new("some_trigger")]),
2365+
period: TriggerPeriod::For,
2366+
events: vec![TriggerEvent::Insert],
2367+
table_name: ObjectName::from(vec![Ident::new("some_table")]),
2368+
referenced_table_name: None,
2369+
referencing: vec![],
2370+
trigger_object: TriggerObject::Statement,
2371+
include_each: false,
2372+
condition: None,
2373+
exec_body: None,
2374+
statements: vec![Statement::Return(ReturnStatement {
2375+
value: None,
2376+
})],
2377+
characteristics: None,
2378+
}
2379+
);
2380+
}

tests/sqlparser_mysql.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3410,13 +3410,14 @@ fn parse_create_trigger() {
34103410
trigger_object: TriggerObject::Row,
34113411
include_each: true,
34123412
condition: None,
3413-
exec_body: TriggerExecBody {
3413+
exec_body: Some(TriggerExecBody {
34143414
exec_type: TriggerExecBodyType::Function,
34153415
func_desc: FunctionDesc {
34163416
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
34173417
args: None,
34183418
}
3419-
},
3419+
}),
3420+
statements: vec![],
34203421
characteristics: None,
34213422
}
34223423
);

0 commit comments

Comments
 (0)