PostgreSQL: Reject string literals as aliases
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index f7c54a9..feccbc8 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs
@@ -1524,6 +1524,14 @@ 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 65bfd46..e49417c 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs
@@ -114,6 +114,12 @@ || RESERVED_EXCLUSIONS_FOR_TABLE_ALIAS.contains(kw) } + /// Aliases are identifiers, see `output_name` and `alias` in + /// <https://www.postgresql.org/docs/current/sql-select.html> + fn supports_string_literal_alias(&self) -> bool { + false + } + /// See <https://www.postgresql.org/docs/current/sql-createoperator.html> fn is_custom_operator_part(&self, ch: char) -> bool { matches!(
diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 340b2d0..aa56d58 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs
@@ -13739,8 +13739,9 @@ 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 1959bcd..61fcb81 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs
@@ -20699,3 +20699,11 @@ } } } + +#[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 11a6b38..1e00288 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs
@@ -10172,3 +10172,33 @@ 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""#); +}