diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index f7c54a9b76..feccbc81c4 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1524,6 +1524,14 @@ pub trait Dialect: Debug + Any { explicit || self.is_table_alias(kw, parser) } + /// Returns true if a single-quoted string can be an alias, as in `SELECT 1 AS 'x'`. + /// + /// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/problems-with-alias.html), + /// [SQLite](https://www.sqlite.org/lang_keywords.html) + fn supports_string_literal_alias(&self) -> bool { + true + } + /// Returns true if this dialect supports querying historical table data /// by specifying which version of the data to query. fn supports_table_versioning(&self) -> bool { diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index 65bfd46a70..e49417cc4d 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -114,6 +114,12 @@ impl Dialect for PostgreSqlDialect { || RESERVED_EXCLUSIONS_FOR_TABLE_ALIAS.contains(kw) } + /// Aliases are identifiers, see `output_name` and `alias` in + /// + fn supports_string_literal_alias(&self) -> bool { + false + } + /// See fn is_custom_operator_part(&self, ch: char) -> bool { matches!( diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 340b2d0779..aa56d58ffe 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -13739,8 +13739,9 @@ impl<'a> Parser<'a> { Token::Word(w) if validator(after_as, &w.keyword, self) => { Ok(Some(w.into_ident(next_token.span))) } - // For backwards-compatibility, we accept quoted strings as aliases regardless of the context. - Token::SingleQuotedString(s) => Ok(Some(Ident::with_quote('\'', s))), + Token::SingleQuotedString(s) if self.dialect.supports_string_literal_alias() => { + Ok(Some(Ident::with_quote('\'', s))) + } Token::DoubleQuotedString(s) => Ok(Some(Ident::with_quote('\"', s))), _ => { if after_as { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 1959bcd345..61fcb8131f 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -20699,3 +20699,11 @@ fn parse_cte_materialization() { } } } + +#[test] +fn parse_string_literal_alias() { + let dialects = all_dialects_where(|d| d.supports_string_literal_alias()); + dialects.one_statement_parses_to("SELECT 1 'x'", "SELECT 1 AS 'x'"); + dialects.verified_stmt("SELECT 1 AS 'x'"); + dialects.verified_stmt("SELECT a FROM t AS 'y'"); +} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 11a6b38713..1e00288673 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -10172,3 +10172,33 @@ fn parse_join_using_alias() { select.to_string() ); } + +#[test] +fn parse_string_literal_alias_rejected() { + for (sql, expected) in [ + ("SELECT 1 'x'", "Expected: end of statement, found: 'x'"), + ( + "SELECT (a) 'x' FROM t", + "Expected: end of statement, found: 'x'", + ), + ( + "SELECT a AS 'x' FROM t", + "Expected: an identifier after AS, found: 'x'", + ), + ( + "SELECT a FROM t 'x'", + "Expected: end of statement, found: 'x'", + ), + ( + "SELECT a FROM t AS 'x'", + "Expected: an identifier after AS, found: 'x'", + ), + ] { + assert_eq!( + pg().parse_sql_statements(sql).unwrap_err(), + ParserError::ParserError(expected.to_string()), + "{sql}" + ); + } + pg().verified_stmt(r#"SELECT a AS "x" FROM t AS "y""#); +}