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]

Reply via email to