From 8d140b07500e1a9b3f27383d096a457f3615daf9 Mon Sep 17 00:00:00 2001 From: Aviv David Date: Wed, 15 Jan 2025 12:16:51 +0200 Subject: [PATCH 1/4] Add support for parsing RAISERROR --- src/ast/mod.rs | 48 ++++++++++++++++++++++++++++++++++++++++ src/ast/spans.rs | 1 + src/keywords.rs | 3 +++ src/parser/mod.rs | 35 +++++++++++++++++++++++++++++ tests/sqlparser_mssql.rs | 21 ++++++++++++++++++ 5 files changed, 108 insertions(+) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 756774353..b3b0e7b68 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3420,6 +3420,37 @@ pub enum Statement { /// /// See Mysql RenameTable(Vec), + /// RaiseError (MSSQL) + /// RAISERROR ( { msg_id | msg_str | @local_variable } + /// { , severity , state } + /// [ , argument [ , ...n ] ] ) + /// [ WITH option [ , ...n ] ] + RaisError { + message: Box, + severity: Box, + state: Box, + arguments: Vec, + options: Vec, + }, +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum RaisErrorOption { + Log, + NoWait, + SetError, +} + +impl fmt::Display for RaisErrorOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + RaisErrorOption::Log => write!(f, "LOG"), + RaisErrorOption::NoWait => write!(f, "NOWAIT"), + RaisErrorOption::SetError => write!(f, "SETERROR"), + } + } } impl fmt::Display for Statement { @@ -4980,6 +5011,23 @@ impl fmt::Display for Statement { Statement::RenameTable(rename_tables) => { write!(f, "RENAME TABLE {}", display_comma_separated(rename_tables)) } + Statement::RaisError { + message, + severity, + state, + arguments, + options, + } => { + write!(f, "RAISERROR({message}, {severity}, {state}")?; + if !arguments.is_empty() { + write!(f, ", {}", display_comma_separated(arguments))?; + } + write!(f, ")")?; + if !options.is_empty() { + write!(f, " WITH {}", display_comma_separated(options))?; + } + Ok(()) + } } } } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index dad0c5379..d19731364 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -493,6 +493,7 @@ impl Spanned for Statement { Statement::LoadData { .. } => Span::empty(), Statement::UNLISTEN { .. } => Span::empty(), Statement::RenameTable { .. } => Span::empty(), + Statement::RaisError { .. } => Span::empty(), } } } diff --git a/src/keywords.rs b/src/keywords.rs index 43abc2b03..273a4b84a 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -460,6 +460,7 @@ define_keywords!( LOCATION, LOCK, LOCKED, + LOG, LOGIN, LOGS, LONGBLOB, @@ -620,6 +621,7 @@ define_keywords!( QUARTER, QUERY, QUOTE, + RAISERROR, RANGE, RANK, RAW, @@ -709,6 +711,7 @@ define_keywords!( SESSION, SESSION_USER, SET, + SETERROR, SETS, SETTINGS, SHARE, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 47d4d6f0d..f2e80277d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -579,6 +579,7 @@ impl<'a> Parser<'a> { Keyword::SAVEPOINT => self.parse_savepoint(), Keyword::RELEASE => self.parse_release(), Keyword::COMMIT => self.parse_commit(), + Keyword::RAISERROR => Ok(self.parse_raiserror()?), Keyword::ROLLBACK => self.parse_rollback(), Keyword::ASSERT => self.parse_assert(), // `PREPARE`, `EXECUTE` and `DEALLOCATE` are Postgres-specific @@ -12816,6 +12817,40 @@ impl<'a> Parser<'a> { } } + pub fn parse_raiserror(&mut self) -> Result { + self.expect_token(&Token::LParen)?; + let message = Box::new(self.parse_expr()?); + self.expect_token(&Token::Comma)?; + let severity = Box::new(self.parse_expr()?); + self.expect_token(&Token::Comma)?; + let state = Box::new(self.parse_expr()?); + let mut arguments = vec![]; + if self.consume_token(&Token::Comma) { + arguments = self.parse_comma_separated(Parser::parse_expr)?; + } + self.expect_token(&Token::RParen)?; + let mut options = vec![]; + if self.parse_keyword(Keyword::WITH) { + options = self.parse_comma_separated(Parser::parse_raiserror_option)?; + } + Ok(Statement::RaisError { + message, + severity, + state, + arguments, + options, + }) + } + + pub fn parse_raiserror_option(&mut self) -> Result { + match self.expect_one_of_keywords(&[Keyword::LOG, Keyword::NOWAIT, Keyword::SETERROR])? { + Keyword::LOG => Ok(RaisErrorOption::Log), + Keyword::NOWAIT => Ok(RaisErrorOption::NoWait), + Keyword::SETERROR => Ok(RaisErrorOption::SetError), + _ => unreachable!(), + } + } + pub fn parse_deallocate(&mut self) -> Result { let prepare = self.parse_keyword(Keyword::PREPARE); let name = self.parse_identifier()?; diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index ecc874af8..3ef78e9c1 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1250,6 +1250,27 @@ fn parse_mssql_declare() { ); } +#[test] +fn test_parse_raiserror() { + let sql = r#"RAISERROR('This is a test', 16, 1)"#; + let _ = ms().verified_stmt(sql); + + let sql = r#"RAISERROR('This is a test', 16, 1) WITH NOWAIT"#; + let _ = ms().verified_stmt(sql); + + let sql = r#"RAISERROR('This is a test', 16, 1, 'ARG') WITH SETERROR, LOG"#; + let _ = ms().verified_stmt(sql); + + let sql = r#"RAISERROR(N'This is message %s %d.', 10, 1, N'number', 5)"#; + let _ = ms().verified_stmt(sql); + + let sql = r#"RAISERROR(N'<<%*.*s>>', 10, 1, 7, 3, N'abcde')"#; + let _ = ms().verified_stmt(sql); + + let sql = r#"RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState)"#; + let _ = ms().verified_stmt(sql); +} + #[test] fn parse_use() { let valid_object_names = [ From b994f380b93ead90c9fd11aaf3bc0b1bd6d5feb0 Mon Sep 17 00:00:00 2001 From: Aviv David Date: Thu, 16 Jan 2025 10:16:44 +0200 Subject: [PATCH 2/4] Add support for parsing RAISERROR --- src/ast/mod.rs | 61 ++++++++++++++++++++-------------------- src/parser/mod.rs | 24 ++++++++++------ tests/sqlparser_mssql.rs | 12 +++++++- 3 files changed, 57 insertions(+), 40 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 53b3f5b73..e6499e14a 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3442,36 +3442,37 @@ pub enum Statement { /// See SetSessionParam(SetSessionParamKind), /// RaiseError (MSSQL) - /// RAISERROR ( { msg_id | msg_str | @local_variable } - /// { , severity , state } - /// [ , argument [ , ...n ] ] ) - /// [ WITH option [ , ...n ] ] - RaisError { - message: Box, - severity: Box, - state: Box, - arguments: Vec, - options: Vec, - }, - } - - #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] - pub enum RaisErrorOption { - Log, - NoWait, - SetError, - } - - impl fmt::Display for RaisErrorOption { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - RaisErrorOption::Log => write!(f, "LOG"), - RaisErrorOption::NoWait => write!(f, "NOWAIT"), - RaisErrorOption::SetError => write!(f, "SETERROR"), - } - } + /// RAISERROR ( { msg_id | msg_str | @local_variable } + /// { , severity , state } + /// [ , argument [ , ...n ] ] ) + /// [ WITH option [ , ...n ] ] + /// See + RaisError { + message: Box, + severity: Box, + state: Box, + arguments: Vec, + options: Vec, + }, +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum RaisErrorOption { + Log, + NoWait, + SetError, +} + +impl fmt::Display for RaisErrorOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + RaisErrorOption::Log => write!(f, "LOG"), + RaisErrorOption::NoWait => write!(f, "NOWAIT"), + RaisErrorOption::SetError => write!(f, "SETERROR"), + } + } } impl fmt::Display for Statement { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f99554154..0dc72bea4 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -13151,6 +13151,7 @@ impl<'a> Parser<'a> { } } + /// Parse a 'RAISERROR' statement pub fn parse_raiserror(&mut self) -> Result { self.expect_token(&Token::LParen)?; let message = Box::new(self.parse_expr()?); @@ -13158,15 +13159,17 @@ impl<'a> Parser<'a> { let severity = Box::new(self.parse_expr()?); self.expect_token(&Token::Comma)?; let state = Box::new(self.parse_expr()?); - let mut arguments = vec![]; - if self.consume_token(&Token::Comma) { - arguments = self.parse_comma_separated(Parser::parse_expr)?; - } + let arguments = if self.consume_token(&Token::Comma) { + self.parse_comma_separated(Parser::parse_expr)? + } else { + vec![] + }; self.expect_token(&Token::RParen)?; - let mut options = vec![]; - if self.parse_keyword(Keyword::WITH) { - options = self.parse_comma_separated(Parser::parse_raiserror_option)?; - } + let options = if self.parse_keyword(Keyword::WITH) { + self.parse_comma_separated(Parser::parse_raiserror_option)? + } else { + vec![] + }; Ok(Statement::RaisError { message, severity, @@ -13181,7 +13184,10 @@ impl<'a> Parser<'a> { Keyword::LOG => Ok(RaisErrorOption::Log), Keyword::NOWAIT => Ok(RaisErrorOption::NoWait), Keyword::SETERROR => Ok(RaisErrorOption::SetError), - _ => unreachable!(), + _ => self.expected( + "LOG, NOWAIT OR SETERROR raiserror option", + self.peek_token(), + ), } } diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 82d0583dc..1d6843ff9 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1253,7 +1253,17 @@ fn parse_mssql_declare() { #[test] fn test_parse_raiserror() { let sql = r#"RAISERROR('This is a test', 16, 1)"#; - let _ = ms().verified_stmt(sql); + let s = ms().verified_stmt(sql); + assert_eq!( + s, + Statement::RaisError { + message: Box::new(Expr::Value(Value::SingleQuotedString("This is a test".to_string()))), + severity: Box::new(Expr::Value(Value::Number("16".to_string(), false))), + state: Box::new(Expr::Value(Value::Number("1".to_string(), false))), + arguments: vec![], + options: vec![], + } + ); let sql = r#"RAISERROR('This is a test', 16, 1) WITH NOWAIT"#; let _ = ms().verified_stmt(sql); From ded60dde1e40e61e82c3c2bc13f63762421eb36f Mon Sep 17 00:00:00 2001 From: Aviv David Date: Thu, 16 Jan 2025 10:16:58 +0200 Subject: [PATCH 3/4] fmt --- tests/sqlparser_mssql.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 1d6843ff9..79a6548ce 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1257,7 +1257,9 @@ fn test_parse_raiserror() { assert_eq!( s, Statement::RaisError { - message: Box::new(Expr::Value(Value::SingleQuotedString("This is a test".to_string()))), + message: Box::new(Expr::Value(Value::SingleQuotedString( + "This is a test".to_string() + ))), severity: Box::new(Expr::Value(Value::Number("16".to_string(), false))), state: Box::new(Expr::Value(Value::Number("1".to_string(), false))), arguments: vec![], From bbe4671e7106a8967aed768b23a22c333a769c7e Mon Sep 17 00:00:00 2001 From: Aviv David Date: Thu, 16 Jan 2025 10:29:48 +0200 Subject: [PATCH 4/4] compilation --- tests/sqlparser_mssql.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 79a6548ce..a0ac8a4d8 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1260,8 +1260,8 @@ fn test_parse_raiserror() { message: Box::new(Expr::Value(Value::SingleQuotedString( "This is a test".to_string() ))), - severity: Box::new(Expr::Value(Value::Number("16".to_string(), false))), - state: Box::new(Expr::Value(Value::Number("1".to_string(), false))), + severity: Box::new(Expr::Value(Value::Number("16".parse().unwrap(), false))), + state: Box::new(Expr::Value(Value::Number("1".parse().unwrap(), false))), arguments: vec![], options: vec![], }