Skip to content

Commit 379434a

Browse files
committed
Add MSSQL THROW statement support
Signed-off-by: Guan-Ming (Wesley) Chiu <105915352+guan404ming@users.noreply.github.com>
1 parent e4c5500 commit 379434a

5 files changed

Lines changed: 91 additions & 0 deletions

File tree

src/ast/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4676,6 +4676,19 @@ pub enum Statement {
46764676
/// Additional `WITH` options for RAISERROR.
46774677
options: Vec<RaisErrorOption>,
46784678
},
4679+
/// Throw (MSSQL)
4680+
/// ```sql
4681+
/// THROW [ error_number, message, state ]
4682+
/// ```
4683+
/// See <https://learn.microsoft.com/en-us/sql/t-sql/language-elements/throw-transact-sql>
4684+
Throw {
4685+
/// Error number expression.
4686+
error_number: Option<Box<Expr>>,
4687+
/// Error message expression.
4688+
message: Option<Box<Expr>>,
4689+
/// State expression.
4690+
state: Option<Box<Expr>>,
4691+
},
46794692
/// ```sql
46804693
/// PRINT msg_str | @local_variable | string_expr
46814694
/// ```
@@ -6120,6 +6133,19 @@ impl fmt::Display for Statement {
61206133
}
61216134
Ok(())
61226135
}
6136+
Statement::Throw {
6137+
error_number,
6138+
message,
6139+
state,
6140+
} => {
6141+
write!(f, "THROW")?;
6142+
if let (Some(error_number), Some(message), Some(state)) =
6143+
(error_number, message, state)
6144+
{
6145+
write!(f, " {error_number}, {message}, {state}")?;
6146+
}
6147+
Ok(())
6148+
}
61236149
Statement::Print(s) => write!(f, "{s}"),
61246150
Statement::Return(r) => write!(f, "{r}"),
61256151
Statement::List(command) => write!(f, "LIST {command}"),

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ impl Spanned for Statement {
481481
Statement::UNLISTEN { .. } => Span::empty(),
482482
Statement::RenameTable { .. } => Span::empty(),
483483
Statement::RaisError { .. } => Span::empty(),
484+
Statement::Throw { .. } => Span::empty(),
484485
Statement::Print { .. } => Span::empty(),
485486
Statement::Return { .. } => Span::empty(),
486487
Statement::List(..) | Statement::Remove(..) => Span::empty(),

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,7 @@ define_keywords!(
10291029
TEXT,
10301030
TEXTFILE,
10311031
THEN,
1032+
THROW,
10321033
TIES,
10331034
TIME,
10341035
TIMEFORMAT,

src/parser/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,7 @@ impl<'a> Parser<'a> {
670670
Keyword::RELEASE => self.parse_release(),
671671
Keyword::COMMIT => self.parse_commit(),
672672
Keyword::RAISERROR => Ok(self.parse_raiserror()?),
673+
Keyword::THROW => Ok(self.parse_throw()?),
673674
Keyword::ROLLBACK => self.parse_rollback(),
674675
Keyword::ASSERT => self.parse_assert(),
675676
// `PREPARE`, `EXECUTE` and `DEALLOCATE` are Postgres-specific
@@ -18260,6 +18261,31 @@ impl<'a> Parser<'a> {
1826018261
}
1826118262
}
1826218263

18264+
/// Parse a MSSQL `THROW` statement
18265+
/// See <https://learn.microsoft.com/en-us/sql/t-sql/language-elements/throw-transact-sql>
18266+
pub fn parse_throw(&mut self) -> Result<Statement, ParserError> {
18267+
// THROW with no arguments is a re-throw inside a CATCH block
18268+
if self.peek_token_ref().token == Token::SemiColon
18269+
|| self.peek_token_ref().token == Token::EOF
18270+
{
18271+
return Ok(Statement::Throw {
18272+
error_number: None,
18273+
message: None,
18274+
state: None,
18275+
});
18276+
}
18277+
let error_number = Box::new(self.parse_expr()?);
18278+
self.expect_token(&Token::Comma)?;
18279+
let message = Box::new(self.parse_expr()?);
18280+
self.expect_token(&Token::Comma)?;
18281+
let state = Box::new(self.parse_expr()?);
18282+
Ok(Statement::Throw {
18283+
error_number: Some(error_number),
18284+
message: Some(message),
18285+
state: Some(state),
18286+
})
18287+
}
18288+
1826318289
/// Parse a SQL `DEALLOCATE` statement
1826418290
pub fn parse_deallocate(&mut self) -> Result<Statement, ParserError> {
1826518291
let prepare = self.parse_keyword(Keyword::PREPARE);

tests/sqlparser_mssql.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,43 @@ fn test_parse_raiserror() {
16651665
let _ = ms().verified_stmt(sql);
16661666
}
16671667

1668+
#[test]
1669+
fn test_parse_throw() {
1670+
// THROW with arguments
1671+
let sql = r#"THROW 51000, 'Record does not exist.', 1"#;
1672+
let s = ms().verified_stmt(sql);
1673+
assert_eq!(
1674+
s,
1675+
Statement::Throw {
1676+
error_number: Some(Box::new(Expr::Value(
1677+
(Value::Number("51000".parse().unwrap(), false)).with_empty_span()
1678+
))),
1679+
message: Some(Box::new(Expr::Value(
1680+
(Value::SingleQuotedString("Record does not exist.".to_string())).with_empty_span()
1681+
))),
1682+
state: Some(Box::new(Expr::Value(
1683+
(Value::Number("1".parse().unwrap(), false)).with_empty_span()
1684+
))),
1685+
}
1686+
);
1687+
1688+
// THROW with variable references
1689+
let sql = r#"THROW @ErrorNumber, @ErrorMessage, @ErrorState"#;
1690+
let _ = ms().verified_stmt(sql);
1691+
1692+
// Re-throw (no arguments)
1693+
let sql = r#"THROW"#;
1694+
let s = ms().verified_stmt(sql);
1695+
assert_eq!(
1696+
s,
1697+
Statement::Throw {
1698+
error_number: None,
1699+
message: None,
1700+
state: None,
1701+
}
1702+
);
1703+
}
1704+
16681705
#[test]
16691706
fn parse_use() {
16701707
let valid_object_names = [

0 commit comments

Comments
 (0)