@@ -5225,6 +5225,10 @@ impl<'a> Parser<'a> {
52255225 let columns = self.parse_comma_separated(Parser::parse_create_statistics_param)?;
52265226 self.expect_keyword_is(Keyword::FROM)?;
52275227 let from = self.parse_comma_separated(Parser::parse_table_and_joins)?;
5228+ // `parse_table_and_joins` is intentionally permissive and supports legacy
5229+ // forms (including single-quoted aliases) for broad compatibility.
5230+ // PostgreSQL `CREATE STATISTICS` is stricter, so we enforce those rules
5231+ // here without tightening FROM parsing globally.
52285232 self.validate_pg_statistics_from_list(&from)?;
52295233
52305234 Ok(CreateStatistics {
@@ -5236,6 +5240,11 @@ impl<'a> Parser<'a> {
52365240 })
52375241 }
52385242
5243+ /// Validate `FROM from_list` after generic table-factor parsing.
5244+ ///
5245+ /// This pass is required because `CREATE STATISTICS` uses PostgreSQL object
5246+ /// name semantics for relation and alias identifiers, while the shared table
5247+ /// parser accepts additional legacy forms in other contexts.
52395248 fn validate_pg_statistics_from_list(&self, from: &[TableWithJoins]) -> Result<(), ParserError> {
52405249 for table_with_joins in from {
52415250 self.validate_pg_statistics_table_with_joins(table_with_joins)?;
@@ -5258,6 +5267,8 @@ impl<'a> Parser<'a> {
52585267 &self,
52595268 relation: &TableFactor,
52605269 ) -> Result<(), ParserError> {
5270+ // Recurse through wrappers so strict identifier checks are applied to
5271+ // every relation/alias position reachable from CREATE STATISTICS FROM.
52615272 match relation {
52625273 TableFactor::Table { name, alias, .. }
52635274 | TableFactor::Function { name, alias, .. }
@@ -5303,6 +5314,7 @@ impl<'a> Parser<'a> {
53035314 }
53045315
53055316 fn validate_pg_statistics_identifier(&self, ident: &Ident) -> Result<(), ParserError> {
5317+ // Single-quoted text is a string literal in PostgreSQL, not an identifier.
53065318 if ident.quote_style == Some('\'') {
53075319 return self.expected(
53085320 "identifier",
0 commit comments