diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a72cb8add..6b7ba12d9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3826,7 +3826,7 @@ pub enum Statement { or_alter: bool, name: ObjectName, params: Option>, - body: Vec, + body: ConditionalStatements, }, /// ```sql /// CREATE MACRO @@ -4705,11 +4705,8 @@ impl fmt::Display for Statement { write!(f, " ({})", display_comma_separated(p))?; } } - write!( - f, - " AS BEGIN {body} END", - body = display_separated(body, "; ") - ) + + write!(f, " AS {body}") } Statement::CreateMacro { or_replace, diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index b754a04f1..70679f38e 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -958,8 +958,14 @@ pub trait Dialect: Debug + Any { /// Returns true if the specified keyword should be parsed as a table factor alias. /// When explicit is true, the keyword is preceded by an `AS` word. Parser is provided /// to enable looking ahead if needed. + /// + /// When the dialect supports statements without semicolon delimiter, actual keywords aren't parsed as aliases. fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool { - explicit || !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw) + if self.supports_statements_without_semicolon_delimiter() { + kw == &Keyword::NoKeyword + } else { + explicit || !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw) + } } /// Returns true if this dialect supports querying historical table data @@ -1021,6 +1027,11 @@ pub trait Dialect: Debug + Any { fn supports_set_names(&self) -> bool { false } + + /// Returns true if the dialect supports parsing statements without a semicolon delimiter. + fn supports_statements_without_semicolon_delimiter(&self) -> bool { + false + } } /// This represents the operators for which precedence must be defined diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs index 647e82a2a..4acf745bf 100644 --- a/src/dialect/mssql.rs +++ b/src/dialect/mssql.rs @@ -63,7 +63,7 @@ impl Dialect for MsSqlDialect { } fn supports_connect_by(&self) -> bool { - true + false } fn supports_eq_alias_assignment(&self) -> bool { @@ -119,6 +119,10 @@ impl Dialect for MsSqlDialect { true } + fn supports_statements_without_semicolon_delimiter(&self) -> bool { + true + } + fn is_column_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool { !keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw) && !RESERVED_FOR_COLUMN_ALIAS.contains(kw) } @@ -271,6 +275,9 @@ impl MsSqlDialect { ) -> Result, ParserError> { let mut stmts = Vec::new(); loop { + while let Token::SemiColon = parser.peek_token_ref().token { + parser.advance_token(); + } if let Token::EOF = parser.peek_token_ref().token { break; } diff --git a/src/keywords.rs b/src/keywords.rs index ddb786650..622b6812e 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -1062,6 +1062,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[ Keyword::ANTI, Keyword::SEMI, Keyword::RETURNING, + Keyword::RETURN, Keyword::ASOF, Keyword::MATCH_CONDITION, // for MSSQL-specific OUTER APPLY (seems reserved in most dialects) @@ -1087,6 +1088,11 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[ Keyword::TABLESAMPLE, Keyword::FROM, Keyword::OPEN, + Keyword::INSERT, + Keyword::UPDATE, + Keyword::DELETE, + Keyword::EXEC, + Keyword::EXECUTE, ]; /// Can't be used as a column alias, so that `SELECT alias` @@ -1115,6 +1121,7 @@ pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[ Keyword::CLUSTER, Keyword::DISTRIBUTE, Keyword::RETURNING, + Keyword::RETURN, // Reserved only as a column alias in the `SELECT` clause Keyword::FROM, Keyword::INTO, @@ -1129,6 +1136,7 @@ pub const RESERVED_FOR_TABLE_FACTOR: &[Keyword] = &[ Keyword::LIMIT, Keyword::HAVING, Keyword::WHERE, + Keyword::RETURN, ]; /// Global list of reserved keywords that cannot be parsed as identifiers @@ -1139,4 +1147,5 @@ pub const RESERVED_FOR_IDENTIFIER: &[Keyword] = &[ Keyword::INTERVAL, Keyword::STRUCT, Keyword::TRIM, + Keyword::RETURN, ]; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index fc6f44376..ccb87f6e9 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -222,6 +222,9 @@ pub struct ParserOptions { /// Controls how literal values are unescaped. See /// [`Tokenizer::with_unescape`] for more details. pub unescape: bool, + /// Determines if the parser requires a semicolon at the end of every statement. + /// (Default: true) + pub require_semicolon_statement_delimiter: bool, } impl Default for ParserOptions { @@ -229,6 +232,7 @@ impl Default for ParserOptions { Self { trailing_commas: false, unescape: true, + require_semicolon_statement_delimiter: true, } } } @@ -261,6 +265,22 @@ impl ParserOptions { self.unescape = unescape; self } + + /// Set if semicolon statement delimiters are required. + /// + /// If this option is `true`, the following SQL will not parse. If the option is `false`, the SQL will parse. + /// + /// ```sql + /// SELECT 1 + /// SELECT 2 + /// ``` + pub fn with_require_semicolon_statement_delimiter( + mut self, + require_semicolon_statement_delimiter: bool, + ) -> Self { + self.require_semicolon_statement_delimiter = require_semicolon_statement_delimiter; + self + } } #[derive(Copy, Clone)] @@ -351,7 +371,11 @@ impl<'a> Parser<'a> { state: ParserState::Normal, dialect, recursion_counter: RecursionCounter::new(DEFAULT_REMAINING_DEPTH), - options: ParserOptions::new().with_trailing_commas(dialect.supports_trailing_commas()), + options: ParserOptions::new() + .with_trailing_commas(dialect.supports_trailing_commas()) + .with_require_semicolon_statement_delimiter( + !dialect.supports_statements_without_semicolon_delimiter(), + ), } } @@ -470,10 +494,10 @@ impl<'a> Parser<'a> { match self.peek_token().token { Token::EOF => break, - // end of statement - Token::Word(word) => { - if expecting_statement_delimiter && word.keyword == Keyword::END { - break; + // don't expect a semicolon statement delimiter after a newline when not otherwise required + Token::Whitespace(Whitespace::Newline) => { + if !self.options.require_semicolon_statement_delimiter { + expecting_statement_delimiter = false; } } _ => {} @@ -485,7 +509,7 @@ impl<'a> Parser<'a> { let statement = self.parse_statement()?; stmts.push(statement); - expecting_statement_delimiter = true; + expecting_statement_delimiter = self.options.require_semicolon_statement_delimiter; } Ok(stmts) } @@ -4513,6 +4537,9 @@ impl<'a> Parser<'a> { ) -> Result, ParserError> { let mut values = vec![]; loop { + // ignore empty statements (between successive statement delimiters) + while self.consume_token(&Token::SemiColon) {} + match &self.peek_nth_token_ref(0).token { Token::EOF => break, Token::Word(w) => { @@ -4524,7 +4551,13 @@ impl<'a> Parser<'a> { } values.push(self.parse_statement()?); - self.expect_token(&Token::SemiColon)?; + + if self.options.require_semicolon_statement_delimiter { + self.expect_token(&Token::SemiColon)?; + } + + // ignore empty statements (between successive statement delimiters) + while self.consume_token(&Token::SemiColon) {} } Ok(values) } @@ -15505,14 +15538,14 @@ impl<'a> Parser<'a> { let name = self.parse_object_name(false)?; let params = self.parse_optional_procedure_parameters()?; self.expect_keyword_is(Keyword::AS)?; - self.expect_keyword_is(Keyword::BEGIN)?; - let statements = self.parse_statements()?; - self.expect_keyword_is(Keyword::END)?; + + let body = self.parse_conditional_statements(&[Keyword::END])?; + Ok(Statement::CreateProcedure { name, or_alter, params, - body: statements, + body, }) } @@ -15639,7 +15672,28 @@ impl<'a> Parser<'a> { /// Parse [Statement::Return] fn parse_return(&mut self) -> Result { - match self.maybe_parse(|p| p.parse_expr())? { + let rs = self.maybe_parse(|p| { + let expr = p.parse_expr()?; + + match &expr { + Expr::Value(_) + | Expr::Function(_) + | Expr::UnaryOp { .. } + | Expr::BinaryOp { .. } + | Expr::Case { .. } + | Expr::Cast { .. } + | Expr::Convert { .. } + | Expr::Subquery(_) => Ok(expr), + // todo: how to retstrict to variables? + Expr::Identifier(id) if id.value.starts_with('@') => Ok(expr), + _ => parser_err!( + "Non-returnable expression found following RETURN", + p.peek_token().span.start + ), + } + })?; + + match rs { Some(expr) => Ok(Statement::Return(ReturnStatement { value: Some(ReturnStatementValue::Expr(expr)), })), diff --git a/src/test_utils.rs b/src/test_utils.rs index 3c22fa911..79e3168e7 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -186,6 +186,37 @@ impl TestedDialects { statements } + /// The same as [`statements_parse_to`] but it will strip semicolons from the SQL text. + pub fn statements_without_semicolons_parse_to( + &self, + sql: &str, + canonical: &str, + ) -> Vec { + let sql_without_semicolons = sql + .replace("; ", " ") + .replace(" ;", " ") + .replace(";\n", "\n") + .replace("\n;", "\n") + .replace(";", " "); + let statements = self + .parse_sql_statements(&sql_without_semicolons) + .expect(&sql_without_semicolons); + if !canonical.is_empty() && sql != canonical { + assert_eq!(self.parse_sql_statements(canonical).unwrap(), statements); + } else { + assert_eq!( + sql, + statements + .iter() + // note: account for format_statement_list manually inserted semicolons + .map(|s| s.to_string().trim_end_matches(";").to_string()) + .collect::>() + .join("; ") + ); + } + statements + } + /// Ensures that `sql` parses as an [`Expr`], and that /// re-serializing the parse result produces canonical pub fn expr_parses_to(&self, sql: &str, canonical: &str) -> Expr { @@ -313,6 +344,43 @@ where all_dialects_where(|d| !except(d)) } +/// Returns all dialects that don't support statements without semicolon delimiters. +/// (i.e. dialects that require semicolon delimiters.) +pub fn all_dialects_requiring_semicolon_statement_delimiter() -> TestedDialects { + let tested_dialects = + all_dialects_except(|d| d.supports_statements_without_semicolon_delimiter()); + assert_ne!(tested_dialects.dialects.len(), 0); + tested_dialects +} + +/// Returns all dialects that do support statements without semicolon delimiters. +/// (i.e. dialects not requiring semicolon delimiters.) +pub fn all_dialects_not_requiring_semicolon_statement_delimiter() -> TestedDialects { + let tested_dialects = + all_dialects_where(|d| d.supports_statements_without_semicolon_delimiter()); + assert_ne!(tested_dialects.dialects.len(), 0); + tested_dialects +} + +/// Asserts an error for `parse_sql_statements`: +/// - "end of statement" for dialects that require semicolon delimiters +/// - "an SQL statement" for dialects that don't require semicolon delimiters. +pub fn assert_err_parse_statements(sql: &str, found: &str) { + assert_eq!( + ParserError::ParserError(format!("Expected: end of statement, found: {}", found)), + all_dialects_requiring_semicolon_statement_delimiter() + .parse_sql_statements(sql) + .unwrap_err() + ); + + assert_eq!( + ParserError::ParserError(format!("Expected: an SQL statement, found: {}", found)), + all_dialects_not_requiring_semicolon_statement_delimiter() + .parse_sql_statements(sql) + .unwrap_err() + ); +} + pub fn assert_eq_vec(expected: &[&str], actual: &[T]) { assert_eq!( expected, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 7a8b8bdaa..b062846ad 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -54,6 +54,9 @@ use sqlparser::ast::DateTimeField::Seconds; use sqlparser::ast::Expr::{Identifier, UnaryOp}; use sqlparser::ast::Value::Number; use sqlparser::test_utils::all_dialects_except; +use sqlparser::test_utils::all_dialects_not_requiring_semicolon_statement_delimiter; +use sqlparser::test_utils::all_dialects_requiring_semicolon_statement_delimiter; +use sqlparser::test_utils::assert_err_parse_statements; #[test] fn parse_numeric_literal_underscore() { @@ -271,20 +274,39 @@ fn parse_insert_default_values() { "INSERT INTO test_table DEFAULT VALUES (some_column)"; assert_eq!( ParserError::ParserError("Expected: end of statement, found: (".to_string()), - parse_sql_statements(insert_with_default_values_and_hive_after_columns).unwrap_err() + all_dialects_requiring_semicolon_statement_delimiter() + .parse_sql_statements(insert_with_default_values_and_hive_after_columns) + .unwrap_err() ); - - let insert_with_default_values_and_hive_partition = - "INSERT INTO test_table DEFAULT VALUES PARTITION (some_column)"; assert_eq!( - ParserError::ParserError("Expected: end of statement, found: PARTITION".to_string()), - parse_sql_statements(insert_with_default_values_and_hive_partition).unwrap_err() + ParserError::ParserError( + "Expected: SELECT, VALUES, or a subquery in the query body, found: some_column" + .to_string() + ), + all_dialects_not_requiring_semicolon_statement_delimiter() + .parse_sql_statements(insert_with_default_values_and_hive_after_columns) + .unwrap_err() + ); + + assert_err_parse_statements( + "INSERT INTO test_table DEFAULT VALUES PARTITION (some_column)", + "PARTITION", ); let insert_with_default_values_and_values_list = "INSERT INTO test_table DEFAULT VALUES (1)"; assert_eq!( ParserError::ParserError("Expected: end of statement, found: (".to_string()), - parse_sql_statements(insert_with_default_values_and_values_list).unwrap_err() + all_dialects_requiring_semicolon_statement_delimiter() + .parse_sql_statements(insert_with_default_values_and_values_list) + .unwrap_err() + ); + assert_eq!( + ParserError::ParserError( + "Expected: SELECT, VALUES, or a subquery in the query body, found: 1".to_string() + ), + all_dialects_not_requiring_semicolon_statement_delimiter() + .parse_sql_statements(insert_with_default_values_and_values_list) + .unwrap_err() ); } @@ -413,11 +435,7 @@ fn parse_update() { ); let sql = "UPDATE t SET a = 1 extrabadstuff"; - let res = parse_sql_statements(sql); - assert_eq!( - ParserError::ParserError("Expected: end of statement, found: extrabadstuff".to_string()), - res.unwrap_err() - ); + assert_err_parse_statements(sql, "extrabadstuff"); } #[test] @@ -648,6 +666,45 @@ fn parse_select_with_table_alias() { ); } +#[test] +fn parse_select_with_table_alias_keyword() { + // note: DECLARE isn't included in RESERVED_FOR_TABLE_ALIAS + let table_alias_non_reserved_keyword = "SELECT a FROM lineitem DECLARE"; + let statements = all_dialects_requiring_semicolon_statement_delimiter() + .parse_sql_statements(table_alias_non_reserved_keyword) + .unwrap(); + assert_eq!(1, statements.len()); + assert_eq!( + ParserError::ParserError("Expected: identifier, found: EOF".to_string()), + all_dialects_not_requiring_semicolon_statement_delimiter() + .parse_sql_statements(table_alias_non_reserved_keyword) + .unwrap_err() + ); + + let table_alias_quoted_keyword = "SELECT a FROM lineitem \"DECLARE\""; + let statements = all_dialects() + .parse_sql_statements(table_alias_quoted_keyword) + .unwrap(); + assert_eq!(1, statements.len()); +} + +#[test] +fn parse_consecutive_queries() { + let select_then_exec = "SELECT * FROM deleted; EXECUTE my_sp 'some', 'params'"; + let _ = all_dialects() + .parse_sql_statements(select_then_exec) + .unwrap(); + let _ = all_dialects_not_requiring_semicolon_statement_delimiter() + .statements_without_semicolons_parse_to(select_then_exec, ""); + + let select_then_update = "SELECT 1 FROM x; UPDATE y SET z = 1"; + let _ = all_dialects() + .parse_sql_statements(select_then_update) + .unwrap(); + let _ = all_dialects_not_requiring_semicolon_statement_delimiter() + .statements_without_semicolons_parse_to(select_then_update, ""); +} + #[test] fn parse_analyze() { verified_stmt("ANALYZE TABLE test_table"); @@ -914,9 +971,18 @@ fn parse_limit() { #[test] fn parse_invalid_limit_by() { - all_dialects() - .parse_sql_statements("SELECT * FROM user BY name") - .expect_err("BY without LIMIT"); + assert_eq!( + ParserError::ParserError("Expected: end of statement, found: name".to_string()), + all_dialects_requiring_semicolon_statement_delimiter() + .parse_sql_statements("SELECT * FROM user BY name") + .unwrap_err() + ); + assert_eq!( + ParserError::ParserError("Expected: an SQL statement, found: BY".to_string()), + all_dialects_not_requiring_semicolon_statement_delimiter() + .parse_sql_statements("SELECT * FROM user BY name") + .unwrap_err() + ); } #[test] @@ -1086,11 +1152,7 @@ fn parse_select_into() { // Do not allow aliases here let sql = "SELECT * INTO table0 asdf FROM table1"; - let result = parse_sql_statements(sql); - assert_eq!( - ParserError::ParserError("Expected: end of statement, found: asdf".to_string()), - result.unwrap_err() - ) + assert_err_parse_statements(sql, "asdf"); } #[test] @@ -1126,11 +1188,7 @@ fn parse_select_wildcard() { ); let sql = "SELECT * + * FROM foo;"; - let result = parse_sql_statements(sql); - assert_eq!( - ParserError::ParserError("Expected: end of statement, found: +".to_string()), - result.unwrap_err(), - ); + assert_err_parse_statements(sql, "+"); } #[test] @@ -1336,11 +1394,7 @@ fn parse_not() { #[test] fn parse_invalid_infix_not() { - let res = parse_sql_statements("SELECT c FROM t WHERE c NOT ("); - assert_eq!( - ParserError::ParserError("Expected: end of statement, found: NOT".to_string()), - res.unwrap_err(), - ); + assert_err_parse_statements("SELECT c FROM t WHERE c NOT (", "NOT"); } #[test] @@ -4718,10 +4772,7 @@ fn parse_rename_table() { _ => unreachable!(), }; - assert_eq!( - parse_sql_statements("RENAME TABLE old_table TO new_table a").unwrap_err(), - ParserError::ParserError("Expected: end of statement, found: a".to_string()) - ); + assert_err_parse_statements("RENAME TABLE old_table TO new_table a", "a"); assert_eq!( parse_sql_statements("RENAME TABLE1 old_table TO new_table a").unwrap_err(), @@ -5059,11 +5110,7 @@ fn parse_alter_table_drop_constraint() { } } - let res = parse_sql_statements("ALTER TABLE tab DROP CONSTRAINT is_active TEXT"); - assert_eq!( - ParserError::ParserError("Expected: end of statement, found: TEXT".to_string()), - res.unwrap_err() - ); + assert_err_parse_statements("ALTER TABLE tab DROP CONSTRAINT is_active TEXT", "TEXT"); } #[test] @@ -5261,10 +5308,13 @@ fn parse_explain_query_plan() { // missing PLAN keyword should return error assert_eq!( ParserError::ParserError("Expected: end of statement, found: SELECT".to_string()), - all_dialects() + all_dialects_requiring_semicolon_statement_delimiter() .parse_sql_statements("EXPLAIN QUERY SELECT sqrt(id) FROM foo") .unwrap_err() ); + assert!(all_dialects_not_requiring_semicolon_statement_delimiter() + .parse_sql_statements("EXPLAIN QUERY SELECT sqrt(id) FROM foo") + .is_ok()); } #[test] @@ -5973,16 +6023,22 @@ fn parse_interval_all() { expr_from_projection(only(&select.projection)), ); - let result = parse_sql_statements("SELECT INTERVAL '1' SECOND TO SECOND"); - assert_eq!( - ParserError::ParserError("Expected: end of statement, found: SECOND".to_string()), - result.unwrap_err(), - ); + assert_err_parse_statements("SELECT INTERVAL '1' SECOND TO SECOND", "SECOND"); - let result = parse_sql_statements("SELECT INTERVAL '10' HOUR (1) TO HOUR (2)"); + let incorrect_hour_interval = "SELECT INTERVAL '10' HOUR (1) TO HOUR (2)"; assert_eq!( ParserError::ParserError("Expected: end of statement, found: (".to_string()), - result.unwrap_err(), + all_dialects_requiring_semicolon_statement_delimiter() + .parse_sql_statements(incorrect_hour_interval) + .unwrap_err(), + ); + assert_eq!( + ParserError::ParserError( + "Expected: SELECT, VALUES, or a subquery in the query body, found: 2".to_string() + ), + all_dialects_not_requiring_semicolon_statement_delimiter() + .parse_sql_statements(incorrect_hour_interval) + .unwrap_err(), ); verified_only_select("SELECT INTERVAL '1' YEAR"); @@ -7575,11 +7631,17 @@ fn parse_multiple_statements() { // Check that extra semicolon at the end is stripped by normalization: one_statement_parses_to(&(sql1.to_owned() + ";"), sql1); // Check that forgetting the semicolon results in an error: - let res = parse_sql_statements(&(sql1.to_owned() + " " + sql2_kw + sql2_rest)); + // (if configured as required by the dialect) + let sql = sql1.to_owned() + " " + sql2_kw + sql2_rest; assert_eq!( ParserError::ParserError("Expected: end of statement, found: ".to_string() + sql2_kw), - res.unwrap_err() + all_dialects_requiring_semicolon_statement_delimiter() + .parse_sql_statements(&sql) + .unwrap_err() ); + assert!(all_dialects_not_requiring_semicolon_statement_delimiter() + .parse_sql_statements(&sql) + .is_ok()); } test_with("SELECT foo", "SELECT", " bar"); // ensure that SELECT/WITH is not parsed as a table or column alias if ';' @@ -8242,11 +8304,7 @@ fn parse_drop_view() { #[test] fn parse_invalid_subquery_without_parens() { - let res = parse_sql_statements("SELECT SELECT 1 FROM bar WHERE 1=1 FROM baz"); - assert_eq!( - ParserError::ParserError("Expected: end of statement, found: 1".to_string()), - res.unwrap_err() - ); + assert_err_parse_statements("SELECT SELECT 1 FROM bar WHERE 1=1 FROM baz", "1"); } #[test] @@ -8472,10 +8530,17 @@ fn lateral_derived() { chk(true); let sql = "SELECT * FROM LATERAL UNNEST ([10,20,30]) as numbers WITH OFFSET;"; - let res = parse_sql_statements(sql); assert_eq!( ParserError::ParserError("Expected: end of statement, found: WITH".to_string()), - res.unwrap_err() + all_dialects_requiring_semicolon_statement_delimiter() + .parse_sql_statements(sql) + .unwrap_err() + ); + assert_eq!( + ParserError::ParserError("Expected: AS, found: ;".to_string()), + all_dialects_not_requiring_semicolon_statement_delimiter() + .parse_sql_statements(sql) + .unwrap_err() ); let sql = "SELECT * FROM a LEFT JOIN LATERAL (b CROSS JOIN c)"; @@ -8604,11 +8669,7 @@ fn parse_start_transaction() { res.unwrap_err() ); - let res = dialects.parse_sql_statements("START TRANSACTION BAD"); - assert_eq!( - ParserError::ParserError("Expected: end of statement, found: BAD".to_string()), - res.unwrap_err() - ); + assert_err_parse_statements("START TRANSACTION BAD", "BAD"); let res = dialects.parse_sql_statements("START TRANSACTION READ ONLY,"); assert_eq!( @@ -9935,23 +9996,9 @@ fn parse_offset_and_limit() { verified_stmt("SELECT foo FROM bar OFFSET 2"); // Can't repeat OFFSET / LIMIT - let res = parse_sql_statements("SELECT foo FROM bar OFFSET 2 OFFSET 2"); - assert_eq!( - ParserError::ParserError("Expected: end of statement, found: OFFSET".to_string()), - res.unwrap_err() - ); - - let res = parse_sql_statements("SELECT foo FROM bar LIMIT 2 LIMIT 2"); - assert_eq!( - ParserError::ParserError("Expected: end of statement, found: LIMIT".to_string()), - res.unwrap_err() - ); - - let res = parse_sql_statements("SELECT foo FROM bar OFFSET 2 LIMIT 2 OFFSET 2"); - assert_eq!( - ParserError::ParserError("Expected: end of statement, found: OFFSET".to_string()), - res.unwrap_err() - ); + assert_err_parse_statements("SELECT foo FROM bar OFFSET 2 OFFSET 2", "OFFSET"); + assert_err_parse_statements("SELECT foo FROM bar LIMIT 2 LIMIT 2", "LIMIT"); + assert_err_parse_statements("SELECT foo FROM bar OFFSET 2 LIMIT 2 OFFSET 2", "OFFSET"); } #[test] @@ -10419,11 +10466,7 @@ fn parse_uncache_table() { } ); - let res = parse_sql_statements("UNCACHE TABLE 'table_name' foo"); - assert_eq!( - ParserError::ParserError("Expected: end of statement, found: foo".to_string()), - res.unwrap_err() - ); + assert_err_parse_statements("UNCACHE TABLE 'table_name' foo", "foo"); let res = parse_sql_statements("UNCACHE 'table_name' foo"); assert_eq!( @@ -10816,7 +10859,9 @@ fn parse_select_table_with_index_hints() { // Test that dialects that don't support table hints will keep parsing the USE as table alias let sql = "SELECT * FROM T USE LIMIT 1"; - let unsupported_dialects = all_dialects_where(|d| !d.supports_table_hints()); + let unsupported_dialects = all_dialects_where(|d| { + !d.supports_table_hints() && !d.supports_statements_without_semicolon_delimiter() + }); let select = unsupported_dialects .verified_only_select_with_canonical(sql, "SELECT * FROM T AS USE LIMIT 1"); assert_eq!( @@ -12909,13 +12954,7 @@ fn test_drop_policy() { "sql parser error: Expected: ON, found: EOF" ); // Wrong option name - assert_eq!( - all_dialects() - .parse_sql_statements("DROP POLICY my_policy ON my_table WRONG") - .unwrap_err() - .to_string(), - "sql parser error: Expected: end of statement, found: WRONG" - ); + assert_err_parse_statements("DROP POLICY my_policy ON my_table WRONG", "WRONG"); } #[test] @@ -12956,18 +12995,27 @@ fn test_alter_policy() { verified_stmt("ALTER POLICY my_policy ON my_table"); // mixing RENAME and APPLY expressions + let sql = "ALTER POLICY old_policy ON my_table TO public RENAME TO new_policy"; assert_eq!( - parse_sql_statements("ALTER POLICY old_policy ON my_table TO public RENAME TO new_policy") + all_dialects_requiring_semicolon_statement_delimiter() + .parse_sql_statements(sql) .unwrap_err() .to_string(), - "sql parser error: Expected: end of statement, found: RENAME" + "sql parser error: Expected: end of statement, found: RENAME".to_string(), ); assert_eq!( - parse_sql_statements("ALTER POLICY old_policy ON my_table RENAME TO new_policy TO public") + all_dialects_not_requiring_semicolon_statement_delimiter() + .parse_sql_statements(sql) .unwrap_err() .to_string(), - "sql parser error: Expected: end of statement, found: TO" + "sql parser error: Expected: KEYWORD `TABLE` after RENAME, found: TO".to_string(), + ); + + assert_err_parse_statements( + "ALTER POLICY old_policy ON my_table RENAME TO new_policy TO public", + "TO", ); + // missing TO in RENAME TO assert_eq!( parse_sql_statements("ALTER POLICY old_policy ON my_table RENAME") @@ -13165,14 +13213,9 @@ fn test_alter_connector() { } // Wrong option name - assert_eq!( - dialects - .parse_sql_statements( - "ALTER CONNECTOR my_connector SET WRONG 'jdbc:mysql://localhost:3306/mydb'" - ) - .unwrap_err() - .to_string(), - "sql parser error: Expected: end of statement, found: WRONG" + assert_err_parse_statements( + "ALTER CONNECTOR my_connector SET WRONG 'jdbc:mysql://localhost:3306/mydb'", + "WRONG", ); } @@ -13916,12 +13959,25 @@ fn parse_create_table_select() { r#"CREATE TABLE foo (baz INT, name STRING) AS SELECT bar, oth_name FROM test.table_a"#; let _ = dialects.one_statement_parses_to(sql_2, expected); - let dialects = all_dialects_where(|d| !d.supports_create_table_select()); + let err_dialects = all_dialects_where(|d| { + !d.supports_create_table_select() && !d.supports_statements_without_semicolon_delimiter() + }); + let multi_statement_dialects = all_dialects_where(|d| { + !d.supports_create_table_select() && d.supports_statements_without_semicolon_delimiter() + }); for sql in [sql_1, sql_2] { assert_eq!( - dialects.parse_sql_statements(sql).unwrap_err(), + err_dialects.parse_sql_statements(sql).unwrap_err(), ParserError::ParserError("Expected: end of statement, found: SELECT".to_string()) ); + + assert_eq!( + multi_statement_dialects + .parse_sql_statements(sql) + .unwrap() + .len(), + 2 + ); } } @@ -14210,7 +14266,17 @@ fn parse_update_from_before_select() { "UPDATE t1 FROM (SELECT name, id FROM t1 GROUP BY id) AS t2 SET name = t2.name FROM (SELECT name from t2) AS t2"; assert_eq!( ParserError::ParserError("Expected: end of statement, found: FROM".to_string()), - parse_sql_statements(query).unwrap_err() + all_dialects_requiring_semicolon_statement_delimiter() + .parse_sql_statements(query) + .unwrap_err() + ); + assert_eq!( + ParserError::ParserError( + "Expected: SELECT, VALUES, or a subquery in the query body, found: FROM".to_string() + ), + all_dialects_not_requiring_semicolon_statement_delimiter() + .parse_sql_statements(query) + .unwrap_err() ); } #[test] @@ -15179,6 +15245,34 @@ fn parse_return() { assert_eq!(stmt, Statement::Return(ReturnStatement { value: None })); let _ = all_dialects().verified_stmt("RETURN 1"); + let _ = all_dialects().verified_stmt("RETURN -1"); + let _ = all_dialects_where(|d| d.is_identifier_start('@')).verified_stmt("RETURN @my_var"); + let _ = all_dialects().verified_stmt("RETURN CAST(1 AS INT)"); + let _ = all_dialects().verified_stmt("RETURN dbo.my_func()"); + let _ = all_dialects().verified_stmt("RETURN (SELECT 1)"); + let _ = all_dialects().verified_stmt("RETURN CASE 1 WHEN 1 THEN 2 END"); + + let _ = all_dialects_where(|d| { + d.is::() + || d.is::() + || d.is::() + || d.is::() + || d.is::() + || d.is::() + || d.is::() + || d.is::() + }) + .verified_stmt("RETURN CONVERT(1, INT)"); + + let _ = all_dialects_except(|d| { + d.is::() + || d.is::() + || d.is::() + || d.is::() + || d.is::() + || d.is::() + }) + .verified_stmt("RETURN CONVERT(1, INT)"); } #[test] diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 9ff55198f..bd01b77c4 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -32,7 +32,10 @@ use sqlparser::ast::DeclareAssignment::MsSqlAssignment; use sqlparser::ast::Value::SingleQuotedString; use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, MsSqlDialect}; -use sqlparser::parser::{Parser, ParserError}; +use sqlparser::parser::{Parser, ParserError, ParserOptions}; + +#[cfg(test)] +use pretty_assertions::assert_eq; #[test] fn parse_mssql_identifiers() { @@ -100,48 +103,52 @@ fn parse_mssql_delimited_identifiers() { #[test] fn parse_create_procedure() { - let sql = "CREATE OR ALTER PROCEDURE test (@foo INT, @bar VARCHAR(256)) AS BEGIN SELECT 1 END"; + let sql = "CREATE OR ALTER PROCEDURE test (@foo INT, @bar VARCHAR(256)) AS BEGIN SELECT 1; END"; assert_eq!( ms().verified_stmt(sql), Statement::CreateProcedure { or_alter: true, - body: vec![Statement::Query(Box::new(Query { - with: None, - limit_clause: None, - fetch: None, - locks: vec![], - for_clause: None, - order_by: None, - settings: None, - format_clause: None, - pipe_operators: vec![], - body: Box::new(SetExpr::Select(Box::new(Select { - select_token: AttachedToken::empty(), - distinct: None, - top: None, - top_before_distinct: false, - projection: vec![SelectItem::UnnamedExpr(Expr::Value( - (number("1")).with_empty_span() - ))], - into: None, - from: vec![], - lateral_views: vec![], - prewhere: None, - selection: None, - group_by: GroupByExpr::Expressions(vec![], vec![]), - cluster_by: vec![], - distribute_by: vec![], - sort_by: vec![], - having: None, - named_window: vec![], - window_before_qualify: false, - qualify: None, - value_table_mode: None, - connect_by: None, - flavor: SelectFlavor::Standard, - }))) - }))], + body: ConditionalStatements::BeginEnd(BeginEndStatements { + begin_token: AttachedToken::empty(), + statements: vec![Statement::Query(Box::new(Query { + with: None, + limit_clause: None, + fetch: None, + locks: vec![], + for_clause: None, + order_by: None, + settings: None, + format_clause: None, + pipe_operators: vec![], + body: Box::new(SetExpr::Select(Box::new(Select { + select_token: AttachedToken::empty(), + distinct: None, + top: None, + top_before_distinct: false, + projection: vec![SelectItem::UnnamedExpr(Expr::Value( + (number("1")).with_empty_span() + ))], + into: None, + from: vec![], + lateral_views: vec![], + prewhere: None, + selection: None, + group_by: GroupByExpr::Expressions(vec![], vec![]), + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + named_window: vec![], + window_before_qualify: false, + qualify: None, + value_table_mode: None, + connect_by: None, + flavor: SelectFlavor::Standard, + }))) + }))], + end_token: AttachedToken::empty(), + }), params: Some(vec![ ProcedureParam { name: Ident { @@ -174,19 +181,24 @@ fn parse_create_procedure() { #[test] fn parse_mssql_create_procedure() { - let _ = ms_and_generic().verified_stmt("CREATE OR ALTER PROCEDURE foo AS BEGIN SELECT 1 END"); - let _ = ms_and_generic().verified_stmt("CREATE PROCEDURE foo AS BEGIN SELECT 1 END"); + let _ = ms_and_generic().verified_stmt("CREATE OR ALTER PROCEDURE foo AS SELECT 1;"); + let _ = ms_and_generic().verified_stmt("CREATE OR ALTER PROCEDURE foo AS BEGIN SELECT 1; END"); + let _ = ms_and_generic().verified_stmt("CREATE PROCEDURE foo AS BEGIN SELECT 1; END"); let _ = ms().verified_stmt( - "CREATE PROCEDURE foo AS BEGIN SELECT [myColumn] FROM [myschema].[mytable] END", + "CREATE PROCEDURE foo AS BEGIN SELECT [myColumn] FROM [myschema].[mytable]; END", ); let _ = ms_and_generic().verified_stmt( - "CREATE PROCEDURE foo (@CustomerName NVARCHAR(50)) AS BEGIN SELECT * FROM DEV END", + "CREATE PROCEDURE foo (@CustomerName NVARCHAR(50)) AS BEGIN SELECT * FROM DEV; END", ); - let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test' END"); + let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; END"); // Test a statement with END in it - let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN SELECT [foo], CASE WHEN [foo] IS NULL THEN 'empty' ELSE 'notempty' END AS [foo] END"); + let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN SELECT [foo], CASE WHEN [foo] IS NULL THEN 'empty' ELSE 'notempty' END AS [foo]; END"); // Multiple statements - let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; SELECT [foo] FROM BAR WHERE [FOO] > 10 END"); + let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; SELECT [foo] FROM BAR WHERE [FOO] > 10; END"); + // early return + let _ = ms().verified_stmt( + "CREATE PROCEDURE [foo] AS BEGIN IF 1 = 0 RETURN;; DECLARE @x INT; RETURN @x; END", + ); } #[test] @@ -237,6 +249,7 @@ fn parse_create_function() { remote_connection: None, }), ); + let _ = ms().statements_without_semicolons_parse_to(return_expression_function, ""); let multi_statement_function = "\ CREATE FUNCTION some_scalar_udf(@foo INT, @bar VARCHAR(256)) \ @@ -248,6 +261,7 @@ fn parse_create_function() { END\ "; let _ = ms().verified_stmt(multi_statement_function); + let _ = ms().statements_without_semicolons_parse_to(multi_statement_function, ""); let create_function_with_conditional = "\ CREATE FUNCTION some_scalar_udf() \ @@ -262,6 +276,7 @@ fn parse_create_function() { END\ "; let _ = ms().verified_stmt(create_function_with_conditional); + let _ = ms().statements_without_semicolons_parse_to(create_function_with_conditional, ""); let create_or_alter_function = "\ CREATE OR ALTER FUNCTION some_scalar_udf(@foo INT, @bar VARCHAR(256)) \ @@ -273,6 +288,7 @@ fn parse_create_function() { END\ "; let _ = ms().verified_stmt(create_or_alter_function); + let _ = ms().statements_without_semicolons_parse_to(create_or_alter_function, ""); let create_function_with_return_expression = "\ CREATE FUNCTION some_scalar_udf(@foo INT, @bar VARCHAR(256)) \ @@ -283,6 +299,7 @@ fn parse_create_function() { END\ "; let _ = ms().verified_stmt(create_function_with_return_expression); + let _ = ms().statements_without_semicolons_parse_to(create_function_with_return_expression, ""); } #[test] @@ -1429,6 +1446,7 @@ fn test_mssql_cursor() { DEALLOCATE Employee_Cursor\ "; let _ = ms().statements_parse_to(full_cursor_usage, ""); + let _ = ms().statements_without_semicolons_parse_to(full_cursor_usage, ""); } #[test] @@ -2043,7 +2061,7 @@ fn parse_mssql_if_else() { // Multiple statements let stmts = ms() - .parse_sql_statements("DECLARE @A INT; IF 1=1 BEGIN SET @A = 1 END ELSE SET @A = 2") + .parse_sql_statements("DECLARE @A INT; IF 1=1 BEGIN SET @A = 1; END ELSE SET @A = 2;") .unwrap(); match &stmts[..] { [Statement::Declare { .. }, Statement::If(stmt)] => { @@ -2058,11 +2076,11 @@ fn parse_mssql_if_else() { #[test] fn test_mssql_if_else_span() { - let sql = "IF 1 = 1 SELECT '1' ELSE SELECT '2'"; + let sql = "IF 1 = 1 SELECT '1'; ELSE SELECT '2';"; let mut parser = Parser::new(&MsSqlDialect {}).try_with_sql(sql).unwrap(); assert_eq!( parser.parse_statement().unwrap().span(), - Span::new(Location::new(1, 1), Location::new(1, sql.len() as u64 + 1)) + Span::new(Location::new(1, 1), Location::new(1, sql.len() as u64)) ); } @@ -2085,7 +2103,7 @@ fn test_mssql_if_else_multiline_span() { #[test] fn test_mssql_if_statements_span() { // Simple statements - let mut sql = "IF 1 = 1 SELECT '1' ELSE SELECT '2'"; + let mut sql = "IF 1 = 1 SELECT '1'; ELSE SELECT '2'"; let mut parser = Parser::new(&MsSqlDialect {}).try_with_sql(sql).unwrap(); match parser.parse_statement().unwrap() { Statement::If(IfStatement { @@ -2099,14 +2117,15 @@ fn test_mssql_if_statements_span() { ); assert_eq!( else_block.span(), - Span::new(Location::new(1, 21), Location::new(1, 36)) + Span::new(Location::new(1, 22), Location::new(1, 37)) ); } stmt => panic!("Unexpected statement: {:?}", stmt), } + let _ = ms().statements_without_semicolons_parse_to(sql, ""); // Blocks - sql = "IF 1 = 1 BEGIN SET @A = 1; END ELSE BEGIN SET @A = 2 END"; + sql = "IF 1 = 1 BEGIN SET @A = 1; END ELSE BEGIN SET @A = 2; END"; parser = Parser::new(&MsSqlDialect {}).try_with_sql(sql).unwrap(); match parser.parse_statement().unwrap() { Statement::If(IfStatement { @@ -2120,11 +2139,12 @@ fn test_mssql_if_statements_span() { ); assert_eq!( else_block.span(), - Span::new(Location::new(1, 32), Location::new(1, 57)) + Span::new(Location::new(1, 32), Location::new(1, 58)) ); } stmt => panic!("Unexpected statement: {:?}", stmt), } + let _ = ms().statements_without_semicolons_parse_to(sql, ""); } #[test] @@ -2271,6 +2291,7 @@ fn parse_create_trigger() { END\ "; let _ = ms().verified_stmt(multi_statement_trigger); + let _ = ms().statements_without_semicolons_parse_to(multi_statement_trigger, ""); let create_trigger_with_return = "\ CREATE TRIGGER some_trigger ON some_table FOR INSERT \ @@ -2280,15 +2301,7 @@ fn parse_create_trigger() { END\ "; let _ = ms().verified_stmt(create_trigger_with_return); - - let create_trigger_with_return = "\ - CREATE TRIGGER some_trigger ON some_table FOR INSERT \ - AS \ - BEGIN \ - RETURN; \ - END\ - "; - let _ = ms().verified_stmt(create_trigger_with_return); + let _ = ms().statements_without_semicolons_parse_to(create_trigger_with_return, ""); let create_trigger_with_conditional = "\ CREATE TRIGGER some_trigger ON some_table FOR INSERT \ @@ -2302,6 +2315,7 @@ fn parse_create_trigger() { END\ "; let _ = ms().verified_stmt(create_trigger_with_conditional); + let _ = ms().statements_without_semicolons_parse_to(create_trigger_with_conditional, ""); } #[test] @@ -2335,3 +2349,315 @@ fn parse_print() { let _ = ms().verified_stmt("PRINT N'Hello, ⛄️!'"); let _ = ms().verified_stmt("PRINT @my_variable"); } + +#[test] +fn test_supports_statements_without_semicolon_delimiter() { + use sqlparser::ast::Ident; + + use sqlparser::tokenizer::Location; + + fn parse_n_statements(n: usize, sql: &str) -> Vec { + let dialect = MsSqlDialect {}; + let parser = Parser::new(&dialect).with_options( + ParserOptions::default().with_require_semicolon_statement_delimiter(false), + ); + let stmts = parser + .try_with_sql(sql) + .unwrap() + .parse_statements() + .unwrap(); + assert_eq!(stmts.len(), n); + stmts + } + + let multiple_statements = "SELECT 1 SELECT 2"; + assert_eq!( + parse_n_statements(2, multiple_statements), + vec![ + Statement::Query(Box::new(Query { + with: None, + limit_clause: None, + fetch: None, + locks: vec![], + for_clause: None, + order_by: None, + settings: None, + format_clause: None, + pipe_operators: vec![], + body: Box::new(SetExpr::Select(Box::new(Select { + select_token: AttachedToken::empty(), + distinct: None, + top: None, + top_before_distinct: false, + projection: vec![SelectItem::UnnamedExpr(Expr::Value( + (Value::Number("1".parse().unwrap(), false)).with_empty_span() + ))], + into: None, + from: vec![], + lateral_views: vec![], + prewhere: None, + selection: None, + group_by: GroupByExpr::Expressions(vec![], vec![]), + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + named_window: vec![], + window_before_qualify: false, + qualify: None, + value_table_mode: None, + connect_by: None, + flavor: SelectFlavor::Standard, + }))), + })), + Statement::Query(Box::new(Query { + with: None, + limit_clause: None, + fetch: None, + locks: vec![], + for_clause: None, + order_by: None, + settings: None, + format_clause: None, + pipe_operators: vec![], + body: Box::new(SetExpr::Select(Box::new(Select { + select_token: AttachedToken::empty(), + distinct: None, + top: None, + top_before_distinct: false, + projection: vec![SelectItem::UnnamedExpr(Expr::Value( + (Value::Number("2".parse().unwrap(), false)).with_empty_span() + ))], + into: None, + from: vec![], + lateral_views: vec![], + prewhere: None, + selection: None, + group_by: GroupByExpr::Expressions(vec![], vec![]), + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + named_window: vec![], + window_before_qualify: false, + qualify: None, + value_table_mode: None, + connect_by: None, + flavor: SelectFlavor::Standard + }))), + })), + ] + ); + + let udf = "CREATE OR ALTER FUNCTION utc_now() + RETURNS SMALLDATETIME \ + AS \ + BEGIN \ + RETURN GETUTCDATE() + END \ + "; + assert_eq!( + parse_n_statements(1, udf)[0], + Statement::CreateFunction(CreateFunction { + or_alter: true, + or_replace: false, + temporary: false, + if_not_exists: false, + name: ObjectName::from(vec![sqlparser::ast::Ident::with_span( + Span::new(Location::new(1, 26), Location::new(1, 33)), + "utc_now" + )]), + args: Some(vec![]), + return_type: Some(sqlparser::ast::DataType::Custom( + ObjectName(vec![sqlparser::ast::ObjectNamePart::Identifier(Ident { + value: "SMALLDATETIME".to_string(), + quote_style: None, + span: Span { + start: Location::new(2, 17), + end: Location::new(2, 30) + }, + })]), + vec![] + )), + function_body: Some(CreateFunctionBody::AsBeginEnd(BeginEndStatements { + begin_token: AttachedToken(TokenWithSpan { + token: Token::Word(Word { + value: "BEGIN".to_string(), + quote_style: None, + keyword: Keyword::BEGIN + }), + span: Span::new(Location::new(2, 47), Location::new(2, 57)), + }), + statements: vec![Statement::Return(ReturnStatement { + value: Some(ReturnStatementValue::Expr(Expr::Function(Function { + name: ObjectName::from(vec![Ident::new("GETUTCDATE")]), + uses_odbc_syntax: false, + parameters: FunctionArguments::None, + args: FunctionArguments::List(FunctionArgumentList { + duplicate_treatment: None, + args: vec![], + clauses: vec![], + }), + filter: None, + null_treatment: None, + over: None, + within_group: vec![], + }))), + })], + end_token: AttachedToken(TokenWithSpan { + token: Token::Word(Word { + value: "END".to_string(), + quote_style: None, + keyword: Keyword::END + }), + span: Span::new(Location::new(3, 9), Location::new(3, 12)), + }) + })), + behavior: None, + called_on_null: None, + parallel: None, + using: None, + language: None, + determinism_specifier: None, + options: None, + remote_connection: None + }) + ); + + let sp = "CREATE OR ALTER PROCEDURE example_sp \ + AS \ + IF USER_NAME() = 'X' \ + RETURN \ + IF 1 = 2 \ + RETURN (SELECT 1) \ + \ + RETURN CONVERT(INT, 123) \ + "; + assert_eq!( + parse_n_statements(1, sp)[0], + Statement::CreateProcedure { + or_alter: true, + name: ObjectName::from(vec![Ident::new("example_sp")]), + params: Some(vec![]), + body: ConditionalStatements::Sequence { + statements: vec![ + Statement::If(IfStatement { + if_block: ConditionalStatementBlock { + start_token: AttachedToken::empty(), + condition: Some(Expr::BinaryOp { + left: Box::new(Expr::Function(Function { + name: ObjectName::from(vec![Ident::new("USER_NAME")]), + uses_odbc_syntax: false, + parameters: FunctionArguments::None, + args: FunctionArguments::List(FunctionArgumentList { + duplicate_treatment: None, + args: vec![], + clauses: vec![], + }), + filter: None, + null_treatment: None, + over: None, + within_group: vec![], + })), + op: BinaryOperator::Eq, + right: Box::new(Expr::Value(ValueWithSpan { + value: Value::SingleQuotedString("X".to_string()), + span: Span::new(Location::new(1, 58), Location::new(1, 61)), + })), + }), + then_token: None, + conditional_statements: ConditionalStatements::Sequence { + statements: vec![Statement::Return(ReturnStatement { + value: None + })], + }, + }, + elseif_blocks: vec![], + else_block: None, + end_token: None, + }), + Statement::If(IfStatement { + if_block: ConditionalStatementBlock { + start_token: AttachedToken::empty(), + condition: Some(Expr::BinaryOp { + left: Box::new(Expr::Value(number("1").with_span(Span::new( + Location::new(1, 73), + Location::new(1, 74) + )))), + op: BinaryOperator::Eq, + right: Box::new(Expr::Value(number("2").with_span(Span::new( + Location::new(1, 76), + Location::new(1, 77) + )))), + }), + then_token: None, + conditional_statements: ConditionalStatements::Sequence { + statements: vec![Statement::Return(ReturnStatement { + value: Some(ReturnStatementValue::Expr(Expr::Subquery( + Box::new(Query { + with: None, + body: Box::new(SetExpr::Select(Box::new(Select { + select_token: AttachedToken::empty(), + distinct: None, + top: None, + top_before_distinct: false, + projection: vec![SelectItem::UnnamedExpr( + Expr::Value(number("1").with_span(Span::new( + Location::new(1, 93), + Location::new(1, 94) + ))) + ),], + into: None, + from: vec![], + lateral_views: vec![], + prewhere: None, + selection: None, + group_by: GroupByExpr::Expressions(vec![], vec![],), + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + named_window: vec![], + qualify: None, + window_before_qualify: false, + value_table_mode: None, + connect_by: None, + flavor: SelectFlavor::Standard, + }),)), + order_by: None, + limit_clause: None, + fetch: None, + locks: vec![], + for_clause: None, + settings: None, + format_clause: None, + pipe_operators: vec![], + }), + ))), + })], + }, + }, + elseif_blocks: vec![], + else_block: None, + end_token: None, + }), + Statement::Return(ReturnStatement { + value: Some(ReturnStatementValue::Expr(Expr::Convert { + is_try: false, + expr: Box::new(Expr::Value( + number("123").with_span(Span::new( + Location::new(1, 89), + Location::new(1, 92) + )) + )), + data_type: Some(DataType::Int(None)), + charset: None, + target_before_value: true, + styles: vec![], + })), + }), + ], + }, + } + ); +} diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 27c60b052..6b85d43e8 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -1367,6 +1367,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() { ParserOptions { trailing_commas: false, unescape: false, + require_semicolon_statement_delimiter: true, } ) .verified_stmt(sql),