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""#);
+}