This is an automated email from the ASF dual-hosted git repository. github-bot pushed a commit to branch gh-readonly-queue/main/pr-2202-75f6f4b17ae316694e877ce15ee81f692ba297a3 in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit 3a42e6527d9072d953dd01e2ba3e3fc91cc0063d Author: Guan-Ming (Wesley) Chiu <[email protected]> AuthorDate: Tue Feb 10 22:48:27 2026 +0800 MSSQL: Support `THROW` statement (#2202) Signed-off-by: Guan-Ming (Wesley) Chiu <[email protected]> Co-authored-by: Ifeanyi Ubah <[email protected]> --- src/ast/mod.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/ast/spans.rs | 1 + src/keywords.rs | 1 + src/parser/mod.rs | 28 ++++++++++++++++++++++++++++ tests/sqlparser_mssql.rs | 37 +++++++++++++++++++++++++++++++++++++ 5 files changed, 111 insertions(+) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 65568b77..601af1bd 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2818,6 +2818,41 @@ impl fmt::Display for RaiseStatementValue { } } +/// A MSSQL `THROW` statement. +/// +/// ```sql +/// THROW [ error_number, message, state ] +/// ``` +/// +/// [MSSQL](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/throw-transact-sql) +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct ThrowStatement { + /// Error number expression. + pub error_number: Option<Box<Expr>>, + /// Error message expression. + pub message: Option<Box<Expr>>, + /// State expression. + pub state: Option<Box<Expr>>, +} + +impl fmt::Display for ThrowStatement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let ThrowStatement { + error_number, + message, + state, + } = self; + + write!(f, "THROW")?; + if let (Some(error_number), Some(message), Some(state)) = (error_number, message, state) { + write!(f, " {error_number}, {message}, {state}")?; + } + Ok(()) + } +} + /// Represents an expression assignment within a variable `DECLARE` statement. /// /// Examples: @@ -4700,6 +4735,8 @@ pub enum Statement { /// Additional `WITH` options for RAISERROR. options: Vec<RaisErrorOption>, }, + /// A MSSQL `THROW` statement. + Throw(ThrowStatement), /// ```sql /// PRINT msg_str | @local_variable | string_expr /// ``` @@ -6157,6 +6194,7 @@ impl fmt::Display for Statement { } Ok(()) } + Statement::Throw(s) => write!(f, "{s}"), Statement::Print(s) => write!(f, "{s}"), Statement::Return(r) => write!(f, "{r}"), Statement::List(command) => write!(f, "LIST {command}"), @@ -11687,6 +11725,12 @@ impl From<RaiseStatement> for Statement { } } +impl From<ThrowStatement> for Statement { + fn from(t: ThrowStatement) -> Self { + Self::Throw(t) + } +} + impl From<Function> for Statement { fn from(f: Function) -> Self { Self::Call(f) diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 8354cea6..f4bdf85a 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -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(), diff --git a/src/keywords.rs b/src/keywords.rs index f84f4d21..7950b191 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -1029,6 +1029,7 @@ define_keywords!( TEXT, TEXTFILE, THEN, + THROW, TIES, TIME, TIMEFORMAT, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2d702978..7a2bda8a 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -670,6 +670,10 @@ impl<'a> Parser<'a> { Keyword::RELEASE => self.parse_release(), Keyword::COMMIT => self.parse_commit(), Keyword::RAISERROR => Ok(self.parse_raiserror()?), + Keyword::THROW => { + self.prev_token(); + self.parse_throw().map(Into::into) + } Keyword::ROLLBACK => self.parse_rollback(), Keyword::ASSERT => self.parse_assert(), // `PREPARE`, `EXECUTE` and `DEALLOCATE` are Postgres-specific @@ -18296,6 +18300,30 @@ impl<'a> Parser<'a> { } } + /// Parse a MSSQL `THROW` statement. + /// + /// See [Statement::Throw] + pub fn parse_throw(&mut self) -> Result<ThrowStatement, ParserError> { + self.expect_keyword_is(Keyword::THROW)?; + + let error_number = self.maybe_parse(|p| p.parse_expr().map(Box::new))?; + let (message, state) = if error_number.is_some() { + self.expect_token(&Token::Comma)?; + let message = Box::new(self.parse_expr()?); + self.expect_token(&Token::Comma)?; + let state = Box::new(self.parse_expr()?); + (Some(message), Some(state)) + } else { + (None, None) + }; + + Ok(ThrowStatement { + error_number, + message, + state, + }) + } + /// Parse a SQL `DEALLOCATE` statement pub fn parse_deallocate(&mut self) -> Result<Statement, ParserError> { let prepare = self.parse_keyword(Keyword::PREPARE); diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 84b8658b..82e6f462 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -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(ThrowStatement { + 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(ThrowStatement { + error_number: None, + message: None, + state: None, + }) + ); +} + #[test] fn parse_use() { let valid_object_names = [ --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
