From 04c8f9bb1dddb14c4390aa0db4823c80cfae0a06 Mon Sep 17 00:00:00 2001 From: Etgar Perets Date: Sun, 17 Aug 2025 11:08:03 +0300 Subject: [PATCH] SGA-3801 Added support for concatenating string literals saperated by space in mySQL --- src/dialect/mod.rs | 6 ++++++ src/dialect/mysql.rs | 5 +++++ src/parser/mod.rs | 43 ++++++++++++++++++++++++++++++++++++++-- tests/sqlparser_mysql.rs | 9 +++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 7cf9d4fd..b82171a5 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -476,6 +476,12 @@ pub trait Dialect: Debug + Any { false } + // Does the Dialect support concatenating of string literal + // Example: SELECT 'Hello ' "world" => SELECT 'Hello world' + fn supports_concat_quoted_identifiers(&self) -> bool { + false + } + /// Does the dialect support trailing commas in the projection list? fn supports_projection_trailing_commas(&self) -> bool { self.supports_trailing_commas() diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs index 6cf24e14..96bb6dd7 100644 --- a/src/dialect/mysql.rs +++ b/src/dialect/mysql.rs @@ -71,6 +71,11 @@ impl Dialect for MySqlDialect { true } + // see + fn supports_concat_quoted_identifiers(&self) -> bool { + true + } + fn ignores_wildcard_escapes(&self) -> bool { true } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 9cddf827..dfd5adf2 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9684,8 +9684,18 @@ impl<'a> Parser<'a> { // bigdecimal feature is enabled, and is otherwise a no-op // (i.e., it returns the input string). Token::Number(n, l) => ok_value(Value::Number(Self::parse(n, span.start)?, l)), - Token::SingleQuotedString(ref s) => ok_value(Value::SingleQuotedString(s.to_string())), - Token::DoubleQuotedString(ref s) => ok_value(Value::DoubleQuotedString(s.to_string())), + Token::SingleQuotedString(ref s) => { + if self.dialect.supports_concat_quoted_identifiers() { + return ok_value(Value::SingleQuotedString(self.combine_quoted(next_token))); + } + ok_value(Value::SingleQuotedString(s.to_string())) + } + Token::DoubleQuotedString(ref s) => { + if self.dialect.supports_concat_quoted_identifiers() { + return ok_value(Value::DoubleQuotedString(self.combine_quoted(next_token))); + } + ok_value(Value::DoubleQuotedString(s.to_string())) + } Token::TripleSingleQuotedString(ref s) => { ok_value(Value::TripleSingleQuotedString(s.to_string())) } @@ -9755,6 +9765,35 @@ impl<'a> Parser<'a> { } } + fn is_quoted_string(&self, token: &Token) -> bool { + matches!( + token, + Token::SingleQuotedString(_) | Token::DoubleQuotedString(_) + ) + } + + fn get_quoted_string(&self, token: &Token) -> String { + match token { + Token::SingleQuotedString(s) => s.clone(), + Token::DoubleQuotedString(s) => s.clone(), + _ => String::new(), + } + } + + fn combine_quoted(&mut self, token: TokenWithSpan) -> String { + let mut combined_string = self.get_quoted_string(&token.token); + loop { + let next_token = self.next_token(); + if !self.is_quoted_string(&next_token.token) { + self.prev_token(); + break; + } + let s = self.get_quoted_string(&next_token.token); + combined_string.push_str(&s); + } + combined_string + } + /// Parse an unsigned numeric literal pub fn parse_number_value(&mut self) -> Result { let value_wrapper = self.parse_value()?; diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index c47750b5..336e1f69 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -4245,3 +4245,12 @@ fn test_create_index_options() { "CREATE INDEX idx_name ON t(c1, c2) USING BTREE LOCK = EXCLUSIVE ALGORITHM = DEFAULT", ); } + +#[test] +fn parse_adjacent_string_literal_concatenation() { + let sql = r#"SELECT 'M' "y" 'S' "q" 'l'"#; + mysql().one_statement_parses_to(sql, r"SELECT 'MySql'"); + + let sql = "SELECT * FROM t WHERE col = 'Hello' \n ' ' \t 'World!'"; + mysql().one_statement_parses_to(sql, r"SELECT * FROM t WHERE col = 'Hello World!'"); +}