Skip to content

Commit 655db9e

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 0d2976d commit 655db9e

File tree

6 files changed

+185
-19
lines changed

6 files changed

+185
-19
lines changed

src/ast/mod.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -3623,6 +3623,7 @@ pub enum Statement {
36233623
/// ```
36243624
///
36253625
/// Postgres: <https://www.postgresql.org/docs/current/sql-createtrigger.html>
3626+
/// SQL Server: <https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql>
36263627
CreateTrigger {
36273628
/// The `OR REPLACE` clause is used to re-create the trigger if it already exists.
36283629
///
@@ -3684,7 +3685,9 @@ pub enum Statement {
36843685
/// Triggering conditions
36853686
condition: Option<Expr>,
36863687
/// Execute logic block
3687-
exec_body: TriggerExecBody,
3688+
exec_body: Option<TriggerExecBody>,
3689+
/// For SQL dialect has statements for a body
3690+
statements: Vec<Statement>,
36883691
/// The characteristic of the trigger, which include whether the trigger is `DEFERRABLE`, `INITIALLY DEFERRED`, or `INITIALLY IMMEDIATE`,
36893692
characteristics: Option<ConstraintCharacteristics>,
36903693
},
@@ -4478,6 +4481,7 @@ impl fmt::Display for Statement {
44784481
condition,
44794482
include_each,
44804483
exec_body,
4484+
statements,
44814485
characteristics,
44824486
} => {
44834487
write!(
@@ -4512,7 +4516,20 @@ impl fmt::Display for Statement {
45124516
if let Some(condition) = condition {
45134517
write!(f, " WHEN {condition}")?;
45144518
}
4515-
write!(f, " EXECUTE {exec_body}")
4519+
if let Some(exec_body) = exec_body {
4520+
write!(f, " EXECUTE {exec_body}")?;
4521+
}
4522+
if !statements.is_empty() {
4523+
write!(f, " AS ")?;
4524+
if statements.len() > 1 {
4525+
write!(f, "BEGIN ")?;
4526+
}
4527+
write!(f, "{}", display_separated(statements, "; "))?;
4528+
if statements.len() > 1 {
4529+
write!(f, " END")?;
4530+
}
4531+
}
4532+
Ok(())
45164533
}
45174534
Statement::DropTrigger {
45184535
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/parser/mod.rs

+57-2
Original file line numberDiff line numberDiff line change
@@ -5208,11 +5208,15 @@ impl<'a> Parser<'a> {
52085208
or_replace: bool,
52095209
is_constraint: bool,
52105210
) -> Result<Statement, ParserError> {
5211-
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect) {
5211+
if !dialect_of!(self is PostgreSqlDialect | GenericDialect | MySqlDialect | MsSqlDialect) {
52125212
self.prev_token();
52135213
return self.expected("an object type after CREATE", self.peek_token());
52145214
}
52155215

5216+
if dialect_of!(self is MsSqlDialect) {
5217+
return self.parse_mssql_create_trigger(or_replace, is_constraint);
5218+
}
5219+
52165220
let name = self.parse_object_name(false)?;
52175221
let period = self.parse_trigger_period()?;
52185222

@@ -5265,18 +5269,69 @@ impl<'a> Parser<'a> {
52655269
trigger_object,
52665270
include_each,
52675271
condition,
5268-
exec_body,
5272+
exec_body: Some(exec_body),
5273+
statements: vec![],
52695274
characteristics,
52705275
})
52715276
}
52725277

5278+
/// Parse `CREATE TRIGGER` for [SQL Server]
5279+
///
5280+
/// [SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql
5281+
pub fn parse_mssql_create_trigger(
5282+
&mut self,
5283+
or_replace: bool,
5284+
is_constraint: bool,
5285+
) -> Result<Statement, ParserError> {
5286+
let name = self.parse_object_name(false)?;
5287+
self.expect_keyword_is(Keyword::ON)?;
5288+
let table_name = self.parse_object_name(false)?;
5289+
let period = self.parse_trigger_period()?;
5290+
let events = self.parse_comma_separated(Parser::parse_trigger_event)?;
5291+
5292+
self.expect_keyword_is(Keyword::AS)?;
5293+
5294+
let statements = if self.peek_keyword(Keyword::BEGIN) {
5295+
self.next_token();
5296+
let mut result = self.parse_statements()?;
5297+
// note: `parse_statements` will consume the `END` token & produce a Commit statement...
5298+
if let Some(Statement::Commit{ chain, end, modifier }) = result.last() {
5299+
if *chain == false && *end == true && *modifier == None {
5300+
result = result[..result.len() - 1].to_vec();
5301+
}
5302+
}
5303+
result
5304+
} else {
5305+
vec![self.parse_statement()?]
5306+
};
5307+
5308+
Ok(Statement::CreateTrigger {
5309+
or_replace,
5310+
is_constraint,
5311+
name,
5312+
period,
5313+
events,
5314+
table_name,
5315+
referenced_table_name: None,
5316+
referencing: Vec::new(),
5317+
trigger_object: TriggerObject::Statement,
5318+
include_each: false,
5319+
condition: None,
5320+
exec_body: None,
5321+
statements,
5322+
characteristics: None,
5323+
})
5324+
}
5325+
52735326
pub fn parse_trigger_period(&mut self) -> Result<TriggerPeriod, ParserError> {
52745327
Ok(
52755328
match self.expect_one_of_keywords(&[
5329+
Keyword::FOR,
52765330
Keyword::BEFORE,
52775331
Keyword::AFTER,
52785332
Keyword::INSTEAD,
52795333
])? {
5334+
Keyword::FOR => TriggerPeriod::For,
52805335
Keyword::BEFORE => TriggerPeriod::Before,
52815336
Keyword::AFTER => TriggerPeriod::After,
52825337
Keyword::INSTEAD => self

tests/sqlparser_mssql.rs

+85
Original file line numberDiff line numberDiff line change
@@ -2036,3 +2036,88 @@ fn parse_mssql_merge_with_output() {
20362036
OUTPUT $action, deleted.ProductID INTO dsi.temp_products";
20372037
ms_and_generic().verified_stmt(stmt);
20382038
}
2039+
2040+
#[test]
2041+
fn parse_create_trigger() {
2042+
let create_trigger = r#"
2043+
CREATE TRIGGER reminder1
2044+
ON Sales.Customer
2045+
AFTER INSERT, UPDATE
2046+
AS RAISERROR ('Notify Customer Relations', 16, 10);
2047+
"#;
2048+
let create_stmt = ms().one_statement_parses_to(create_trigger, "");
2049+
assert_eq!(
2050+
create_stmt,
2051+
Statement::CreateTrigger {
2052+
or_replace: false,
2053+
is_constraint: false,
2054+
name: ObjectName::from(vec![Ident::new("reminder1")]),
2055+
period: TriggerPeriod::After,
2056+
events: vec![
2057+
TriggerEvent::Insert,
2058+
TriggerEvent::Update(vec![]),
2059+
],
2060+
table_name: ObjectName::from(vec![Ident::new("Sales"), Ident::new("Customer")]),
2061+
referenced_table_name: None,
2062+
referencing: vec![],
2063+
trigger_object: TriggerObject::Statement,
2064+
include_each: false,
2065+
condition: None,
2066+
exec_body: None,
2067+
statements: vec![Statement::RaisError {
2068+
message: Box::new(Expr::Value(
2069+
(Value::SingleQuotedString("Notify Customer Relations".to_string())).with_empty_span()
2070+
)),
2071+
severity: Box::new(Expr::Value(
2072+
(Value::Number("16".parse().unwrap(), false)).with_empty_span()
2073+
)),
2074+
state: Box::new(Expr::Value(
2075+
(Value::Number("10".parse().unwrap(), false)).with_empty_span()
2076+
)),
2077+
arguments: vec![],
2078+
options: vec![],
2079+
}],
2080+
characteristics: None,
2081+
}
2082+
);
2083+
2084+
let multi_statement_trigger = r#"
2085+
CREATE TRIGGER some_trigger ON some_table FOR INSERT
2086+
AS
2087+
BEGIN
2088+
RAISERROR('Trigger fired', 10, 1);
2089+
END
2090+
"#;
2091+
let create_stmt = ms().one_statement_parses_to(multi_statement_trigger, "");
2092+
assert_eq!(
2093+
create_stmt,
2094+
Statement::CreateTrigger {
2095+
or_replace: false,
2096+
is_constraint: false,
2097+
name: ObjectName::from(vec![Ident::new("some_trigger")]),
2098+
period: TriggerPeriod::For,
2099+
events: vec![TriggerEvent::Insert],
2100+
table_name: ObjectName::from(vec![Ident::new("some_table")]),
2101+
referenced_table_name: None,
2102+
referencing: vec![],
2103+
trigger_object: TriggerObject::Statement,
2104+
include_each: false,
2105+
condition: None,
2106+
exec_body: None,
2107+
statements: vec![Statement::RaisError {
2108+
message: Box::new(Expr::Value(
2109+
(Value::SingleQuotedString("Trigger fired".to_string())).with_empty_span()
2110+
)),
2111+
severity: Box::new(Expr::Value(
2112+
(Value::Number("10".parse().unwrap(), false)).with_empty_span()
2113+
)),
2114+
state: Box::new(Expr::Value(
2115+
(Value::Number("1".parse().unwrap(), false)).with_empty_span()
2116+
)),
2117+
arguments: vec![],
2118+
options: vec![],
2119+
}],
2120+
characteristics: None,
2121+
}
2122+
);
2123+
}

tests/sqlparser_mysql.rs

+3-2
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
);

tests/sqlparser_postgres.rs

+19-13
Original file line numberDiff line numberDiff line change
@@ -5059,13 +5059,14 @@ fn parse_create_simple_before_insert_trigger() {
50595059
trigger_object: TriggerObject::Row,
50605060
include_each: true,
50615061
condition: None,
5062-
exec_body: TriggerExecBody {
5062+
exec_body: Some(TriggerExecBody {
50635063
exec_type: TriggerExecBodyType::Function,
50645064
func_desc: FunctionDesc {
50655065
name: ObjectName::from(vec![Ident::new("check_account_insert")]),
50665066
args: None,
50675067
},
5068-
},
5068+
}),
5069+
statements: vec![],
50695070
characteristics: None,
50705071
};
50715072

@@ -5094,13 +5095,14 @@ fn parse_create_after_update_trigger_with_condition() {
50945095
op: BinaryOperator::Gt,
50955096
right: Box::new(Expr::value(number("10000"))),
50965097
}))),
5097-
exec_body: TriggerExecBody {
5098+
exec_body: Some(TriggerExecBody {
50985099
exec_type: TriggerExecBodyType::Function,
50995100
func_desc: FunctionDesc {
51005101
name: ObjectName::from(vec![Ident::new("check_account_update")]),
51015102
args: None,
51025103
},
5103-
},
5104+
}),
5105+
statements: vec![],
51045106
characteristics: None,
51055107
};
51065108

@@ -5122,13 +5124,14 @@ fn parse_create_instead_of_delete_trigger() {
51225124
trigger_object: TriggerObject::Row,
51235125
include_each: true,
51245126
condition: None,
5125-
exec_body: TriggerExecBody {
5127+
exec_body: Some(TriggerExecBody {
51265128
exec_type: TriggerExecBodyType::Function,
51275129
func_desc: FunctionDesc {
51285130
name: ObjectName::from(vec![Ident::new("check_account_deletes")]),
51295131
args: None,
51305132
},
5131-
},
5133+
}),
5134+
statements: vec![],
51325135
characteristics: None,
51335136
};
51345137

@@ -5154,13 +5157,14 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() {
51545157
trigger_object: TriggerObject::Row,
51555158
include_each: true,
51565159
condition: None,
5157-
exec_body: TriggerExecBody {
5160+
exec_body: Some(TriggerExecBody {
51585161
exec_type: TriggerExecBodyType::Function,
51595162
func_desc: FunctionDesc {
51605163
name: ObjectName::from(vec![Ident::new("check_account_changes")]),
51615164
args: None,
51625165
},
5163-
},
5166+
}),
5167+
statements: vec![],
51645168
characteristics: Some(ConstraintCharacteristics {
51655169
deferrable: Some(true),
51665170
initially: Some(DeferrableInitial::Deferred),
@@ -5197,13 +5201,14 @@ fn parse_create_trigger_with_referencing() {
51975201
trigger_object: TriggerObject::Row,
51985202
include_each: true,
51995203
condition: None,
5200-
exec_body: TriggerExecBody {
5204+
exec_body: Some(TriggerExecBody {
52015205
exec_type: TriggerExecBodyType::Function,
52025206
func_desc: FunctionDesc {
52035207
name: ObjectName::from(vec![Ident::new("check_account_referencing")]),
52045208
args: None,
52055209
},
5206-
},
5210+
}),
5211+
statements: vec![],
52075212
characteristics: None,
52085213
};
52095214

@@ -5223,7 +5228,7 @@ fn parse_create_trigger_invalid_cases() {
52235228
),
52245229
(
52255230
"CREATE TRIGGER check_update TOMORROW UPDATE ON accounts EXECUTE FUNCTION check_account_update",
5226-
"Expected: one of BEFORE or AFTER or INSTEAD, found: TOMORROW"
5231+
"Expected: one of FOR or BEFORE or AFTER or INSTEAD, found: TOMORROW"
52275232
),
52285233
(
52295234
"CREATE TRIGGER check_update BEFORE SAVE ON accounts EXECUTE FUNCTION check_account_update",
@@ -5496,13 +5501,14 @@ fn parse_trigger_related_functions() {
54965501
trigger_object: TriggerObject::Row,
54975502
include_each: true,
54985503
condition: None,
5499-
exec_body: TriggerExecBody {
5504+
exec_body: Some(TriggerExecBody {
55005505
exec_type: TriggerExecBodyType::Function,
55015506
func_desc: FunctionDesc {
55025507
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
55035508
args: None,
55045509
}
5505-
},
5510+
}),
5511+
statements: vec![],
55065512
characteristics: None
55075513
}
55085514
);

0 commit comments

Comments
 (0)