Skip to content

Add support for RAISE statement #1766

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 18, 2025
Merged
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
56 changes: 56 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2256,6 +2256,57 @@ impl fmt::Display for ConditionalStatements {
}
}

/// A `RAISE` statement.
///
/// Examples:
/// ```sql
/// RAISE USING MESSAGE = 'error';
///
/// RAISE myerror;
/// ```
///
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#raise)
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/snowflake-scripting/raise)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct RaiseStatement {
pub value: Option<RaiseStatementValue>,
}

impl fmt::Display for RaiseStatement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let RaiseStatement { value } = self;

write!(f, "RAISE")?;
if let Some(value) = value {
write!(f, " {value}")?;
}

Ok(())
}
}

/// Represents the error value of a [RaiseStatement].
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum RaiseStatementValue {
/// `RAISE USING MESSAGE = 'error'`
UsingMessage(Expr),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can messages be arbitrary expressions (or must they always be strings?)

I think it is fine to use Expr here, I was just curious

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah they can be arbitrary expressions, bigquery for example would accept this sql

begin
 select 1;
 exception when error then
   raise using message = 2+3;
 end;

/// `RAISE myerror`
Expr(Expr),
}

impl fmt::Display for RaiseStatementValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RaiseStatementValue::Expr(expr) => write!(f, "{expr}"),
RaiseStatementValue::UsingMessage(expr) => write!(f, "USING MESSAGE = {expr}"),
}
}
}

/// Represents an expression assignment within a variable `DECLARE` statement.
///
/// Examples:
Expand Down Expand Up @@ -2827,6 +2878,8 @@ pub enum Statement {
Case(CaseStatement),
/// An `IF` statement.
If(IfStatement),
/// A `RAISE` statement.
Raise(RaiseStatement),
/// ```sql
/// CALL <function>
/// ```
Expand Down Expand Up @@ -4142,6 +4195,9 @@ impl fmt::Display for Statement {
Statement::If(stmt) => {
write!(f, "{stmt}")
}
Statement::Raise(stmt) => {
write!(f, "{stmt}")
}
Statement::AttachDatabase {
schema_name,
database_file_name,
Expand Down
28 changes: 23 additions & 5 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ use super::{
JoinOperator, JsonPath, JsonPathElem, LateralView, LimitClause, MatchRecognizePattern, Measure,
NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction,
OnInsert, OrderBy, OrderByExpr, OrderByKind, Partition, PivotValueSource, ProjectionSelect,
Query, ReferentialAction, RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select,
SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias,
TableAliasColumnDef, TableConstraint, TableFactor, TableObject, TableOptionsClustered,
TableWithJoins, UpdateTableFromKind, Use, Value, Values, ViewColumnDef,
WildcardAdditionalOptions, With, WithFill,
Query, RaiseStatement, RaiseStatementValue, ReferentialAction, RenameSelectItem,
ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption,
Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint,
TableFactor, TableObject, TableOptionsClustered, TableWithJoins, UpdateTableFromKind, Use,
Value, Values, ViewColumnDef, WildcardAdditionalOptions, With, WithFill,
};

/// Given an iterator of spans, return the [Span::union] of all spans.
Expand Down Expand Up @@ -337,6 +337,7 @@ impl Spanned for Statement {
} => source.span(),
Statement::Case(stmt) => stmt.span(),
Statement::If(stmt) => stmt.span(),
Statement::Raise(stmt) => stmt.span(),
Statement::Call(function) => function.span(),
Statement::Copy {
source,
Expand Down Expand Up @@ -782,6 +783,23 @@ impl Spanned for ConditionalStatements {
}
}

impl Spanned for RaiseStatement {
fn span(&self) -> Span {
let RaiseStatement { value } = self;

union_spans(value.iter().map(|value| value.span()))
}
}

impl Spanned for RaiseStatementValue {
fn span(&self) -> Span {
match self {
RaiseStatementValue::UsingMessage(expr) => expr.span(),
RaiseStatementValue::Expr(expr) => expr.span(),
}
}
}

/// # partial span
///
/// Missing spans:
Expand Down
2 changes: 2 additions & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ define_keywords!(
MEDIUMTEXT,
MEMBER,
MERGE,
MESSAGE,
METADATA,
METHOD,
METRIC,
Expand Down Expand Up @@ -695,6 +696,7 @@ define_keywords!(
QUARTER,
QUERY,
QUOTE,
RAISE,
RAISERROR,
RANGE,
RANK,
Expand Down
20 changes: 20 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,10 @@ impl<'a> Parser<'a> {
self.prev_token();
self.parse_if_stmt()
}
Keyword::RAISE => {
self.prev_token();
self.parse_raise_stmt()
}
Keyword::SELECT | Keyword::WITH | Keyword::VALUES | Keyword::FROM => {
self.prev_token();
self.parse_query().map(Statement::Query)
Expand Down Expand Up @@ -719,6 +723,22 @@ impl<'a> Parser<'a> {
})
}

/// Parse a `RAISE` statement.
///
/// See [Statement::Raise]
pub fn parse_raise_stmt(&mut self) -> Result<Statement, ParserError> {
self.expect_keyword_is(Keyword::RAISE)?;

let value = if self.parse_keywords(&[Keyword::USING, Keyword::MESSAGE]) {
self.expect_token(&Token::Eq)?;
Some(RaiseStatementValue::UsingMessage(self.parse_expr()?))
} else {
self.maybe_parse(|parser| parser.parse_expr().map(RaiseStatementValue::Expr))?
};

Ok(Statement::Raise(RaiseStatement { value }))
}

pub fn parse_comment(&mut self) -> Result<Statement, ParserError> {
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);

Expand Down
23 changes: 23 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14298,6 +14298,29 @@ fn parse_if_statement() {
);
}

#[test]
fn parse_raise_statement() {
let sql = "RAISE USING MESSAGE = 42";
let Statement::Raise(stmt) = verified_stmt(sql) else {
unreachable!()
};
assert_eq!(
Some(RaiseStatementValue::UsingMessage(Expr::value(number("42")))),
stmt.value
);

verified_stmt("RAISE USING MESSAGE = 'error'");
verified_stmt("RAISE myerror");
verified_stmt("RAISE 42");
verified_stmt("RAISE using");
verified_stmt("RAISE");

assert_eq!(
ParserError::ParserError("Expected: =, found: error".to_string()),
parse_sql_statements("RAISE USING MESSAGE error").unwrap_err()
);
}

#[test]
fn test_lambdas() {
let dialects = all_dialects_where(|d| d.supports_lambda_functions());
Expand Down