Add CREATE TRIGGER support for SQL Server (#1810)

This commit is contained in:
Andrew Harper 2025-05-03 10:59:13 -04:00 committed by GitHub
parent 728645fb31
commit a497358c3a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 255 additions and 41 deletions

View file

@ -273,6 +273,16 @@ fn parse_create_function() {
END\
";
let _ = ms().verified_stmt(create_or_alter_function);
let create_function_with_return_expression = "\
CREATE FUNCTION some_scalar_udf(@foo INT, @bar VARCHAR(256)) \
RETURNS INT \
AS \
BEGIN \
RETURN CONVERT(INT, 1) + 2; \
END\
";
let _ = ms().verified_stmt(create_function_with_return_expression);
}
#[test]
@ -2199,6 +2209,101 @@ fn parse_mssql_merge_with_output() {
ms_and_generic().verified_stmt(stmt);
}
#[test]
fn parse_create_trigger() {
let create_trigger = "\
CREATE OR ALTER TRIGGER reminder1 \
ON Sales.Customer \
AFTER INSERT, UPDATE \
AS RAISERROR('Notify Customer Relations', 16, 10);\
";
let create_stmt = ms().verified_stmt(create_trigger);
assert_eq!(
create_stmt,
Statement::CreateTrigger {
or_alter: true,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("reminder1")]),
period: TriggerPeriod::After,
events: vec![TriggerEvent::Insert, TriggerEvent::Update(vec![]),],
table_name: ObjectName::from(vec![Ident::new("Sales"), Ident::new("Customer")]),
referenced_table_name: None,
referencing: vec![],
trigger_object: TriggerObject::Statement,
include_each: false,
condition: None,
exec_body: None,
statements: Some(ConditionalStatements::Sequence {
statements: vec![Statement::RaisError {
message: Box::new(Expr::Value(
(Value::SingleQuotedString("Notify Customer Relations".to_string()))
.with_empty_span()
)),
severity: Box::new(Expr::Value(
(Value::Number("16".parse().unwrap(), false)).with_empty_span()
)),
state: Box::new(Expr::Value(
(Value::Number("10".parse().unwrap(), false)).with_empty_span()
)),
arguments: vec![],
options: vec![],
}],
}),
characteristics: None,
}
);
let multi_statement_as_trigger = "\
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
AS \
DECLARE @var INT; \
RAISERROR('Trigger fired', 10, 1);\
";
let _ = ms().verified_stmt(multi_statement_as_trigger);
let multi_statement_trigger = "\
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
AS \
BEGIN \
DECLARE @var INT; \
RAISERROR('Trigger fired', 10, 1); \
END\
";
let _ = ms().verified_stmt(multi_statement_trigger);
let create_trigger_with_return = "\
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
AS \
BEGIN \
RETURN; \
END\
";
let _ = ms().verified_stmt(create_trigger_with_return);
let create_trigger_with_return = "\
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
AS \
BEGIN \
RETURN; \
END\
";
let _ = ms().verified_stmt(create_trigger_with_return);
let create_trigger_with_conditional = "\
CREATE TRIGGER some_trigger ON some_table FOR INSERT \
AS \
BEGIN \
IF 1 = 2 \
BEGIN \
RAISERROR('Trigger fired', 10, 1); \
END; \
RETURN; \
END\
";
let _ = ms().verified_stmt(create_trigger_with_conditional);
}
#[test]
fn parse_drop_trigger() {
let sql_drop_trigger = "DROP TRIGGER emp_stamp;";

View file

@ -3779,6 +3779,7 @@ fn parse_create_trigger() {
assert_eq!(
create_stmt,
Statement::CreateTrigger {
or_alter: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
@ -3790,13 +3791,14 @@ fn parse_create_trigger() {
trigger_object: TriggerObject::Row,
include_each: true,
condition: None,
exec_body: TriggerExecBody {
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,
func_desc: FunctionDesc {
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
args: None,
}
},
}),
statements: None,
characteristics: None,
}
);

View file

@ -5157,6 +5157,7 @@ fn test_escaped_string_literal() {
fn parse_create_simple_before_insert_trigger() {
let sql = "CREATE TRIGGER check_insert BEFORE INSERT ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_insert";
let expected = Statement::CreateTrigger {
or_alter: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("check_insert")]),
@ -5168,13 +5169,14 @@ fn parse_create_simple_before_insert_trigger() {
trigger_object: TriggerObject::Row,
include_each: true,
condition: None,
exec_body: TriggerExecBody {
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,
func_desc: FunctionDesc {
name: ObjectName::from(vec![Ident::new("check_account_insert")]),
args: None,
},
},
}),
statements: None,
characteristics: None,
};
@ -5185,6 +5187,7 @@ fn parse_create_simple_before_insert_trigger() {
fn parse_create_after_update_trigger_with_condition() {
let sql = "CREATE TRIGGER check_update AFTER UPDATE ON accounts FOR EACH ROW WHEN (NEW.balance > 10000) EXECUTE FUNCTION check_account_update";
let expected = Statement::CreateTrigger {
or_alter: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("check_update")]),
@ -5203,13 +5206,14 @@ fn parse_create_after_update_trigger_with_condition() {
op: BinaryOperator::Gt,
right: Box::new(Expr::value(number("10000"))),
}))),
exec_body: TriggerExecBody {
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,
func_desc: FunctionDesc {
name: ObjectName::from(vec![Ident::new("check_account_update")]),
args: None,
},
},
}),
statements: None,
characteristics: None,
};
@ -5220,6 +5224,7 @@ fn parse_create_after_update_trigger_with_condition() {
fn parse_create_instead_of_delete_trigger() {
let sql = "CREATE TRIGGER check_delete INSTEAD OF DELETE ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_deletes";
let expected = Statement::CreateTrigger {
or_alter: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("check_delete")]),
@ -5231,13 +5236,14 @@ fn parse_create_instead_of_delete_trigger() {
trigger_object: TriggerObject::Row,
include_each: true,
condition: None,
exec_body: TriggerExecBody {
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,
func_desc: FunctionDesc {
name: ObjectName::from(vec![Ident::new("check_account_deletes")]),
args: None,
},
},
}),
statements: None,
characteristics: None,
};
@ -5248,6 +5254,7 @@ fn parse_create_instead_of_delete_trigger() {
fn parse_create_trigger_with_multiple_events_and_deferrable() {
let sql = "CREATE CONSTRAINT TRIGGER check_multiple_events BEFORE INSERT OR UPDATE OR DELETE ON accounts DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION check_account_changes";
let expected = Statement::CreateTrigger {
or_alter: false,
or_replace: false,
is_constraint: true,
name: ObjectName::from(vec![Ident::new("check_multiple_events")]),
@ -5263,13 +5270,14 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() {
trigger_object: TriggerObject::Row,
include_each: true,
condition: None,
exec_body: TriggerExecBody {
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,
func_desc: FunctionDesc {
name: ObjectName::from(vec![Ident::new("check_account_changes")]),
args: None,
},
},
}),
statements: None,
characteristics: Some(ConstraintCharacteristics {
deferrable: Some(true),
initially: Some(DeferrableInitial::Deferred),
@ -5284,6 +5292,7 @@ fn parse_create_trigger_with_multiple_events_and_deferrable() {
fn parse_create_trigger_with_referencing() {
let sql = "CREATE TRIGGER check_referencing BEFORE INSERT ON accounts REFERENCING NEW TABLE AS new_accounts OLD TABLE AS old_accounts FOR EACH ROW EXECUTE FUNCTION check_account_referencing";
let expected = Statement::CreateTrigger {
or_alter: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("check_referencing")]),
@ -5306,13 +5315,14 @@ fn parse_create_trigger_with_referencing() {
trigger_object: TriggerObject::Row,
include_each: true,
condition: None,
exec_body: TriggerExecBody {
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,
func_desc: FunctionDesc {
name: ObjectName::from(vec![Ident::new("check_account_referencing")]),
args: None,
},
},
}),
statements: None,
characteristics: None,
};
@ -5332,7 +5342,7 @@ fn parse_create_trigger_invalid_cases() {
),
(
"CREATE TRIGGER check_update TOMORROW UPDATE ON accounts EXECUTE FUNCTION check_account_update",
"Expected: one of BEFORE or AFTER or INSTEAD, found: TOMORROW"
"Expected: one of FOR or BEFORE or AFTER or INSTEAD, found: TOMORROW"
),
(
"CREATE TRIGGER check_update BEFORE SAVE ON accounts EXECUTE FUNCTION check_account_update",
@ -5590,6 +5600,7 @@ fn parse_trigger_related_functions() {
assert_eq!(
create_trigger,
Statement::CreateTrigger {
or_alter: false,
or_replace: false,
is_constraint: false,
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
@ -5601,13 +5612,14 @@ fn parse_trigger_related_functions() {
trigger_object: TriggerObject::Row,
include_each: true,
condition: None,
exec_body: TriggerExecBody {
exec_body: Some(TriggerExecBody {
exec_type: TriggerExecBodyType::Function,
func_desc: FunctionDesc {
name: ObjectName::from(vec![Ident::new("emp_stamp")]),
args: None,
}
},
}),
statements: None,
characteristics: None
}
);