-
Notifications
You must be signed in to change notification settings - Fork 601
Add CREATE TRIGGER
support for SQL Server
#1810
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
39517ce
to
b94916f
Compare
b94916f
to
bd6d624
Compare
This fails to parse on this branch, but should:
Fixed, never mind |
FYI for reviewers I rebased this on the create function branch, due to the return statement logic here: #1808 (comment) |
bd6d624
to
aaa2ab3
Compare
77776fc
to
d5d376e
Compare
d5d376e
to
d2b564d
Compare
Rebased again now that #1808 has been merged. Should be ready for review. |
src/parser/mod.rs
Outdated
if dialect_of!(self is MsSqlDialect) { | ||
return self.parse_mssql_create_trigger(or_alter, or_replace, is_constraint); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe this could be parsed via self.dialect.parse_statement()
for the mssql version? since the dialect_of macro we try to stay away for new code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That seems tricky. Can you help me understand what that code would look like? parse_create_trigger is happening inside a complicated branch of parse_create, so it seems like that logic from src/parser/mod.rs would need to be copy/pasted to src/dialect/mssql.rs which seems unfortunate.
Otherwise I don't know how to have the dialect's parse_statement implement custom logic only for "create trigger" but not for all the other "create xxx"'s
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yeah it would need to manually parse the alter, replace for the trigger, I agree that its not ideal but its probably the lesser evil, see snowflake here for an example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, thanks for the reference. The patterns in this library are difficult to interpret sometimes (what's best practice vs what's deprecated). I've moved the trigger parsing logic into the dialect. Doing it this way also means we need to be able to rewind the parser if it's some form of "create" not handled particularly in the dialect, so I also added a parser method to reset the index in that case accordingly.
1f80e42
to
d042e88
Compare
src/dialect/mssql.rs
Outdated
let original_index = parser.index(); | ||
|
||
if !parser.parse_keyword(Keyword::CREATE) { | ||
parser.set_index(original_index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think instead of manually tracking index we can rely on the parser.maybe_parse()
? that will automatically handle the indexing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a commit to the branch for this approach.
I'm not sure it's strictly better because it seems more difficult to extend if/when additional CREATE
things are moved into dialects, if this is a general pattern. Perhaps you can clear up what I'm concerned about here -- with maybe_parse if parsing fails inside the dialect, it'll rewind the parser & report None up the chain. Then, parsing will continue as if it was common code. However that might not be what we prefer -- if we're already into parse_create_trigger and get a parsing error, we actually do want it to fail right there rather than rewinding. In the former approach that seemed clear. With this approach it seems like the dialect specific parser failure will get ignored (and you will probably get another parser error from the general code instead).
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes you're right, if failure occurs while parsing the trigger body for example the ideal behavior in this case would be to report that error instead of returning None. In which case maybe_parse isn't ideal.
I think the main goal is avoiding a custom implementation to track indexing here, looking at the new diff it seems the sqlserver version is simpler so maybe something like this suffices already?
if self.parse_keywords(CREATE, TRIGGER) {
Some(self.parse_create_trigger(self, parser, false))
} else if self.parse_keywords(CREATE, OR, ALTER, TRIGGER) {
Some(self.parse_create_trigger(self, parser, true))
} else {
None
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, I'll drop the maybe_parse commit & update the implementation to this two branch approach. I think that will meet both our expectations for the time being.
A pro/con on that iteration will be that the dialect implementation of parse_statement is mixing peek & parse strategies in that function, which seems unfortunate. However further refactoring it to consolidate strategies seems outside the scope of the branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
144c049
to
dc7eef0
Compare
src/dialect/mssql.rs
Outdated
|
||
parser.expect_keyword_is(Keyword::AS)?; | ||
|
||
let trigger_statements_body = if parser.peek_keyword(Keyword::BEGIN) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar approach in the other PR for stored procedures that may/not have begin/end tokens https://github.com/apache/datafusion-sqlparser-rs/pull/1834/files#r2069458988
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also updated the related stored procedure PR accordingly: #1834 (comment)
23a063f
to
06ae263
Compare
src/dialect/mssql.rs
Outdated
BeginEndStatements { | ||
begin_token: AttachedToken(begin_token), | ||
statements, | ||
end_token: AttachedToken(end_token), | ||
} | ||
} else { | ||
BeginEndStatements { | ||
begin_token: AttachedToken::empty(), | ||
statements: parser.parse_statements()?, | ||
end_token: AttachedToken::empty(), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I think instead of representing the begin optionality using tokens, we can use an enum, something similar to ConditionalStatements where we have an explicit BeginEnd
enum variant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, yeah I could see it either way. I will update this to use the enum pattern
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
- 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
06ae263
to
000fe3f
Compare
1727546
to
ef6aa55
Compare
Adjacent to: #1808 with similar considerations, and temporarily rebased on it (eg, this branch should probably wait for that branch to merge).
This PR introduces support for parsing
CREATE TRIGGER
for SQL Server.The main concern is that for the existing dialects, there was an expectation of an
EXECUTE
keyword (PG: https://www.postgresql.org/docs/current/sql-createtrigger.html). However, SQL Server doesn't use this syntax and instead supports multi statement blocks (like a stored procedure).The difficulty here in the codebase is what to do about CreateTrigger.exec_body & TriggerExecBody. In this iteration I made the property an
Option
, which seemed like the least impact on existing code. TriggerExecBody, etc can be left alone.However in the future I think this could be improved with a more broad refactoring, such as a TriggerBody enum, which can consolidate TriggerExecBodyType's options & use
Vec<Statements>
for aMultiStatement
variant andFunctionDesc
forFunction
&Procedure
variants. That'd basically remove the TriggerExecBody struct, too. Overall I think that'd be a cleaner approach for the CreateTrigger struct.That's all speculative followup work, for now I just want to be able to parse most SQL Server triggers. Other SQL Server specific things like particular trigger options, DDL trigger stuff, etc can come later as needed.