Skip to content

Commit 3a42e65

Browse files
guan404mingiffyio
andauthored
MSSQL: Support THROW statement (#2202)
Signed-off-by: Guan-Ming (Wesley) Chiu <105915352+guan404ming@users.noreply.github.com> Co-authored-by: Ifeanyi Ubah <7816405+iffyio@users.noreply.github.com>
1 parent 75f6f4b commit 3a42e65

File tree

5 files changed

+111
-0
lines changed

5 files changed

+111
-0
lines changed

src/ast/mod.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2818,6 +2818,41 @@ impl fmt::Display for RaiseStatementValue {
28182818
}
28192819
}
28202820

2821+
/// A MSSQL `THROW` statement.
2822+
///
2823+
/// ```sql
2824+
/// THROW [ error_number, message, state ]
2825+
/// ```
2826+
///
2827+
/// [MSSQL](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/throw-transact-sql)
2828+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2829+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2830+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
2831+
pub struct ThrowStatement {
2832+
/// Error number expression.
2833+
pub error_number: Option<Box<Expr>>,
2834+
/// Error message expression.
2835+
pub message: Option<Box<Expr>>,
2836+
/// State expression.
2837+
pub state: Option<Box<Expr>>,
2838+
}
2839+
2840+
impl fmt::Display for ThrowStatement {
2841+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2842+
let ThrowStatement {
2843+
error_number,
2844+
message,
2845+
state,
2846+
} = self;
2847+
2848+
write!(f, "THROW")?;
2849+
if let (Some(error_number), Some(message), Some(state)) = (error_number, message, state) {
2850+
write!(f, " {error_number}, {message}, {state}")?;
2851+
}
2852+
Ok(())
2853+
}
2854+
}
2855+
28212856
/// Represents an expression assignment within a variable `DECLARE` statement.
28222857
///
28232858
/// Examples:
@@ -4700,6 +4735,8 @@ pub enum Statement {
47004735
/// Additional `WITH` options for RAISERROR.
47014736
options: Vec<RaisErrorOption>,
47024737
},
4738+
/// A MSSQL `THROW` statement.
4739+
Throw(ThrowStatement),
47034740
/// ```sql
47044741
/// PRINT msg_str | @local_variable | string_expr
47054742
/// ```
@@ -6157,6 +6194,7 @@ impl fmt::Display for Statement {
61576194
}
61586195
Ok(())
61596196
}
6197+
Statement::Throw(s) => write!(f, "{s}"),
61606198
Statement::Print(s) => write!(f, "{s}"),
61616199
Statement::Return(r) => write!(f, "{r}"),
61626200
Statement::List(command) => write!(f, "LIST {command}"),
@@ -11687,6 +11725,12 @@ impl From<RaiseStatement> for Statement {
1168711725
}
1168811726
}
1168911727

11728+
impl From<ThrowStatement> for Statement {
11729+
fn from(t: ThrowStatement) -> Self {
11730+
Self::Throw(t)
11731+
}
11732+
}
11733+
1169011734
impl From<Function> for Statement {
1169111735
fn from(f: Function) -> Self {
1169211736
Self::Call(f)

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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,10 @@ 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 => {
674+
self.prev_token();
675+
self.parse_throw().map(Into::into)
676+
}
673677
Keyword::ROLLBACK => self.parse_rollback(),
674678
Keyword::ASSERT => self.parse_assert(),
675679
// `PREPARE`, `EXECUTE` and `DEALLOCATE` are Postgres-specific
@@ -18296,6 +18300,30 @@ impl<'a> Parser<'a> {
1829618300
}
1829718301
}
1829818302

18303+
/// Parse a MSSQL `THROW` statement.
18304+
///
18305+
/// See [Statement::Throw]
18306+
pub fn parse_throw(&mut self) -> Result<ThrowStatement, ParserError> {
18307+
self.expect_keyword_is(Keyword::THROW)?;
18308+
18309+
let error_number = self.maybe_parse(|p| p.parse_expr().map(Box::new))?;
18310+
let (message, state) = if error_number.is_some() {
18311+
self.expect_token(&Token::Comma)?;
18312+
let message = Box::new(self.parse_expr()?);
18313+
self.expect_token(&Token::Comma)?;
18314+
let state = Box::new(self.parse_expr()?);
18315+
(Some(message), Some(state))
18316+
} else {
18317+
(None, None)
18318+
};
18319+
18320+
Ok(ThrowStatement {
18321+
error_number,
18322+
message,
18323+
state,
18324+
})
18325+
}
18326+
1829918327
/// Parse a SQL `DEALLOCATE` statement
1830018328
pub fn parse_deallocate(&mut self) -> Result<Statement, ParserError> {
1830118329
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(ThrowStatement {
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(ThrowStatement {
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)