Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4676,6 +4676,19 @@ pub enum Statement {
/// Additional `WITH` options for RAISERROR.
options: Vec<RaisErrorOption>,
},
/// Throw (MSSQL)
/// ```sql
/// THROW [ error_number, message, state ]
/// ```
/// See <https://learn.microsoft.com/en-us/sql/t-sql/language-elements/throw-transact-sql>
Throw {
/// Error number expression.
error_number: Option<Box<Expr>>,
/// Error message expression.
message: Option<Box<Expr>>,
/// State expression.
state: Option<Box<Expr>>,
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're slowly moving away from this representation over to having all statements wrapped in a named struct. As a result, can we do something like this instead?

struct Throw { ... }
Statement::Throw(Throw)

See the RAISE statement for an example

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your information. I just update with wrapped named struct.

/// ```sql
/// PRINT msg_str | @local_variable | string_expr
/// ```
Expand Down Expand Up @@ -6120,6 +6133,19 @@ impl fmt::Display for Statement {
}
Ok(())
}
Statement::Throw {
error_number,
message,
state,
} => {
write!(f, "THROW")?;
if let (Some(error_number), Some(message), Some(state)) =
(error_number, message, state)
{
write!(f, " {error_number}, {message}, {state}")?;
}
Ok(())
}
Statement::Print(s) => write!(f, "{s}"),
Statement::Return(r) => write!(f, "{r}"),
Statement::List(command) => write!(f, "LIST {command}"),
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ impl Spanned for Statement {
Statement::UNLISTEN { .. } => Span::empty(),
Statement::RenameTable { .. } => Span::empty(),
Statement::RaisError { .. } => Span::empty(),
Statement::Throw { .. } => Span::empty(),
Statement::Print { .. } => Span::empty(),
Statement::Return { .. } => Span::empty(),
Statement::List(..) | Statement::Remove(..) => Span::empty(),
Expand Down
1 change: 1 addition & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,7 @@ define_keywords!(
TEXT,
TEXTFILE,
THEN,
THROW,
TIES,
TIME,
TIMEFORMAT,
Expand Down
26 changes: 26 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@ impl<'a> Parser<'a> {
Keyword::RELEASE => self.parse_release(),
Keyword::COMMIT => self.parse_commit(),
Keyword::RAISERROR => Ok(self.parse_raiserror()?),
Keyword::THROW => Ok(self.parse_throw()?),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Keyword::THROW => Ok(self.parse_throw()?),
Keyword::THROW => {
self.prev_token();
self.parse_throw().map(Into::into)
},

see RAISE for an example. we're moving to have the statement parsing functions return the actual struct instead of a Statement enum variant. Also it would be ideal that the parse_throw is a standalone function (able to parse a THROW statement) so we rewind the token before invoking it

Keyword::ROLLBACK => self.parse_rollback(),
Keyword::ASSERT => self.parse_assert(),
// `PREPARE`, `EXECUTE` and `DEALLOCATE` are Postgres-specific
Expand Down Expand Up @@ -18260,6 +18261,31 @@ impl<'a> Parser<'a> {
}
}

/// Parse a MSSQL `THROW` statement
/// See <https://learn.microsoft.com/en-us/sql/t-sql/language-elements/throw-transact-sql>
pub fn parse_throw(&mut self) -> Result<Statement, ParserError> {
// THROW with no arguments is a re-throw inside a CATCH block
if self.peek_token_ref().token == Token::SemiColon
|| self.peek_token_ref().token == Token::EOF
{
return Ok(Statement::Throw {
error_number: None,
message: None,
state: None,
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.peek_token_ref().token == Token::SemiColon
|| self.peek_token_ref().token == Token::EOF
{
return Ok(Statement::Throw {
error_number: None,
message: None,
state: None,
});
}

I think we can skip this logic, it would be expected for the function to return an error if the input is empty (per the previous comment about making this function stand-alone)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, that makes sense to me. Just moved~

let error_number = Box::new(self.parse_expr()?);
self.expect_token(&Token::Comma)?;
let message = Box::new(self.parse_expr()?);
self.expect_token(&Token::Comma)?;
let state = Box::new(self.parse_expr()?);
Ok(Statement::Throw {
error_number: Some(error_number),
message: Some(message),
state: Some(state),
})
}

/// Parse a SQL `DEALLOCATE` statement
pub fn parse_deallocate(&mut self) -> Result<Statement, ParserError> {
let prepare = self.parse_keyword(Keyword::PREPARE);
Expand Down
37 changes: 37 additions & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1665,6 +1665,43 @@ fn test_parse_raiserror() {
let _ = ms().verified_stmt(sql);
}

#[test]
fn test_parse_throw() {
// THROW with arguments
let sql = r#"THROW 51000, 'Record does not exist.', 1"#;
let s = ms().verified_stmt(sql);
assert_eq!(
s,
Statement::Throw {
error_number: Some(Box::new(Expr::Value(
(Value::Number("51000".parse().unwrap(), false)).with_empty_span()
))),
message: Some(Box::new(Expr::Value(
(Value::SingleQuotedString("Record does not exist.".to_string())).with_empty_span()
))),
state: Some(Box::new(Expr::Value(
(Value::Number("1".parse().unwrap(), false)).with_empty_span()
))),
}
);

// THROW with variable references
let sql = r#"THROW @ErrorNumber, @ErrorMessage, @ErrorState"#;
let _ = ms().verified_stmt(sql);

// Re-throw (no arguments)
let sql = r#"THROW"#;
let s = ms().verified_stmt(sql);
assert_eq!(
s,
Statement::Throw {
error_number: None,
message: None,
state: None,
}
);
}

#[test]
fn parse_use() {
let valid_object_names = [
Expand Down