diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a59519695..9b3e8c2e1 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2816,6 +2816,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>, + /// Error message expression. + pub message: Option>, + /// State expression. + pub state: Option>, +} + +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: @@ -4676,6 +4711,8 @@ pub enum Statement { /// Additional `WITH` options for RAISERROR. options: Vec, }, + /// A MSSQL `THROW` statement. + Throw(ThrowStatement), /// ```sql /// PRINT msg_str | @local_variable | string_expr /// ``` @@ -6120,6 +6157,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}"), @@ -11650,6 +11688,12 @@ impl From for Statement { } } +impl From for Statement { + fn from(t: ThrowStatement) -> Self { + Self::Throw(t) + } +} + impl From for Statement { fn from(f: Function) -> Self { Self::Call(f) diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 16a9a926f..9c50663a3 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 f84f4d213..7950b1918 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 14ddd2b50..53d1d2299 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 @@ -18260,6 +18264,30 @@ impl<'a> Parser<'a> { } } + /// Parse a MSSQL `THROW` statement. + /// + /// See [Statement::Throw] + pub fn parse_throw(&mut self) -> Result { + 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 { let prepare = self.parse_keyword(Keyword::PREPARE); diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 84b8658b0..82e6f4621 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 = [