@@ -5269,6 +5269,10 @@ impl<'a> Parser<'a> {
52695269 let columns = self.parse_comma_separated(Parser::parse_create_statistics_param)?;
52705270 self.expect_keyword_is(Keyword::FROM)?;
52715271 let from = self.parse_comma_separated(Parser::parse_table_and_joins)?;
5272+ // `parse_table_and_joins` is intentionally permissive and supports legacy
5273+ // forms (including single-quoted aliases) for broad compatibility.
5274+ // PostgreSQL `CREATE STATISTICS` is stricter, so we enforce those rules
5275+ // here without tightening FROM parsing globally.
52725276 self.validate_pg_statistics_from_list(&from)?;
52735277
52745278 Ok(CreateStatistics {
@@ -5280,6 +5284,11 @@ impl<'a> Parser<'a> {
52805284 })
52815285 }
52825286
5287+ /// Validate `FROM from_list` after generic table-factor parsing.
5288+ ///
5289+ /// This pass is required because `CREATE STATISTICS` uses PostgreSQL object
5290+ /// name semantics for relation and alias identifiers, while the shared table
5291+ /// parser accepts additional legacy forms in other contexts.
52835292 fn validate_pg_statistics_from_list(&self, from: &[TableWithJoins]) -> Result<(), ParserError> {
52845293 for table_with_joins in from {
52855294 self.validate_pg_statistics_table_with_joins(table_with_joins)?;
@@ -5302,6 +5311,8 @@ impl<'a> Parser<'a> {
53025311 &self,
53035312 relation: &TableFactor,
53045313 ) -> Result<(), ParserError> {
5314+ // Recurse through wrappers so strict identifier checks are applied to
5315+ // every relation/alias position reachable from CREATE STATISTICS FROM.
53055316 match relation {
53065317 TableFactor::Table { name, alias, .. }
53075318 | TableFactor::Function { name, alias, .. }
@@ -5347,6 +5358,7 @@ impl<'a> Parser<'a> {
53475358 }
53485359
53495360 fn validate_pg_statistics_identifier(&self, ident: &Ident) -> Result<(), ParserError> {
5361+ // Single-quoted text is a string literal in PostgreSQL, not an identifier.
53505362 if ident.quote_style == Some('\'') {
53515363 return self.expected(
53525364 "identifier",
0 commit comments