Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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:
Expand Down Expand Up @@ -4676,6 +4711,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
/// ```
Expand Down Expand Up @@ -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}"),
Expand Down Expand Up @@ -11650,6 +11688,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)
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,7 @@ define_keywords!(
TEXT,
TEXTFILE,
THEN,
THROW,
TIES,
TIME,
TIMEFORMAT,
Expand Down
28 changes: 28 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -18260,6 +18264,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);
Expand Down
37 changes: 37 additions & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down