agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
267+ messages / 2 participants
[nested] [flat]

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 267+ messages in thread

* [PATCH v2 1/1] Remove unnecessary volatile qualifiers.
@ 2026-07-06 15:24  Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 267+ messages in thread

From: Nathan Bossart @ 2026-07-06 15:24 UTC (permalink / raw)

This commit cleans up volatile qualifiers that fit the below
criteria:

* Accesses to shared memory protected by a spinlock or LWLock.
Before commit 0709b7ee72, callers had to use volatile when
accessing spinlock-protected shared memory.  Since spinlock
acquire/release became compiler barriers, and because LWLocks
provide the same guarantee, that is no longer necessary.  These
either predate that change or were cargo-culted from code that did.

* Pointers used only to find the address of a member.  The volatile
qualifier only affects accesses made by dereferencing the pointer,
so it is unnecessary there.

* Accesses to struct members that are marked volatile in the struct
definition.  There's no need to mark these pointers volatile,
either.

* Leftovers from removed PG_TRY blocks.  These were marked volatile
to protect a value that is modified inside a PG_TRY block and used
afterward, but the PG_TRY has since been removed.
---
 src/backend/access/transam/clog.c     |  7 +++---
 src/backend/catalog/index.c           |  2 +-
 src/backend/commands/async.c          |  4 ++--
 src/backend/replication/syncrep.c     | 19 +++++++---------
 src/backend/storage/ipc/procsignal.c  |  8 +++----
 src/backend/storage/ipc/shm_toc.c     | 31 ++++++++++++---------------
 src/backend/storage/lmgr/lock.c       |  2 +-
 src/backend/storage/lmgr/proc.c       |  3 +--
 src/test/modules/test_shm_mq/setup.c  |  4 ++--
 src/test/modules/test_shm_mq/worker.c |  2 +-
 10 files changed, 37 insertions(+), 45 deletions(-)

diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 75012d4b8f0..6f7f6b86eb6 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -450,7 +450,6 @@ static bool
 TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
 								XLogRecPtr lsn, int64 pageno)
 {
-	volatile PROC_HDR *procglobal = ProcGlobal;
 	PGPROC	   *proc = MyProc;
 	uint32		nextidx;
 	uint32		wakeidx;
@@ -493,7 +492,7 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
 	 * different from ours.  If another group starts to update a page in the
 	 * same bank as ours, they wait until we release the lock.
 	 */
-	nextidx = pg_atomic_read_u32(&procglobal->clogGroupFirst);
+	nextidx = pg_atomic_read_u32(&ProcGlobal->clogGroupFirst);
 
 	while (true)
 	{
@@ -524,7 +523,7 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
 
 		pg_atomic_write_u32(&proc->clogGroupNext, nextidx);
 
-		if (pg_atomic_compare_exchange_u32(&procglobal->clogGroupFirst,
+		if (pg_atomic_compare_exchange_u32(&ProcGlobal->clogGroupFirst,
 										   &nextidx,
 										   (uint32) MyProcNumber))
 			break;
@@ -576,7 +575,7 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
 	 * At this point, any processes trying to do this would create a separate
 	 * group.
 	 */
-	nextidx = pg_atomic_exchange_u32(&procglobal->clogGroupFirst,
+	nextidx = pg_atomic_exchange_u32(&ProcGlobal->clogGroupFirst,
 									 INVALID_PROC_NUMBER);
 
 	/* Remember head of list so we can perform wakeups after dropping lock. */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..81bba4beac7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3637,7 +3637,7 @@ reindex_index(const ReindexStmt *stmt, Oid indexId,
 	int			save_sec_context;
 	int			save_nestlevel;
 	IndexInfo  *indexInfo;
-	volatile bool skipped_constraint = false;
+	bool		skipped_constraint = false;
 	PGRUsage	ru0;
 	bool		progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0);
 	bool		set_tablespace = false;
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index eee8bc29f38..2799f989b96 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -605,7 +605,7 @@ static void CleanupListenersOnExit(void);
 static bool IsListeningOn(const char *channel);
 static void asyncQueueUnregister(void);
 static bool asyncQueueIsFull(void);
-static bool asyncQueueAdvance(volatile QueuePosition *position, int entryLength);
+static bool asyncQueueAdvance(QueuePosition *position, int entryLength);
 static void asyncQueueNotificationToEntry(Notification *n, AsyncQueueEntry *qe);
 static ListCell *asyncQueueAddEntries(ListCell *nextNotify);
 static double asyncQueueUsage(void);
@@ -1968,7 +1968,7 @@ asyncQueueIsFull(void)
  * returns true, else false.
  */
 static bool
-asyncQueueAdvance(volatile QueuePosition *position, int entryLength)
+asyncQueueAdvance(QueuePosition *position, int entryLength)
 {
 	int64		pageno = QUEUE_POS_PAGE(*position);
 	int			offset = QUEUE_POS_OFFSET(*position);
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index e0e30579c59..d870f09e0a0 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -483,7 +483,6 @@ SyncRepInitConfig(void)
 void
 SyncRepReleaseWaiters(void)
 {
-	volatile WalSndCtlData *walsndctl = WalSndCtl;
 	XLogRecPtr	writePtr;
 	XLogRecPtr	flushPtr;
 	XLogRecPtr	applyPtr;
@@ -558,19 +557,19 @@ SyncRepReleaseWaiters(void)
 	 * Set the lsn first so that when we wake backends they will release up to
 	 * this location.
 	 */
-	if (walsndctl->lsn[SYNC_REP_WAIT_WRITE] < writePtr)
+	if (WalSndCtl->lsn[SYNC_REP_WAIT_WRITE] < writePtr)
 	{
-		walsndctl->lsn[SYNC_REP_WAIT_WRITE] = writePtr;
+		WalSndCtl->lsn[SYNC_REP_WAIT_WRITE] = writePtr;
 		numwrite = SyncRepWakeQueue(false, SYNC_REP_WAIT_WRITE);
 	}
-	if (walsndctl->lsn[SYNC_REP_WAIT_FLUSH] < flushPtr)
+	if (WalSndCtl->lsn[SYNC_REP_WAIT_FLUSH] < flushPtr)
 	{
-		walsndctl->lsn[SYNC_REP_WAIT_FLUSH] = flushPtr;
+		WalSndCtl->lsn[SYNC_REP_WAIT_FLUSH] = flushPtr;
 		numflush = SyncRepWakeQueue(false, SYNC_REP_WAIT_FLUSH);
 	}
-	if (walsndctl->lsn[SYNC_REP_WAIT_APPLY] < applyPtr)
+	if (WalSndCtl->lsn[SYNC_REP_WAIT_APPLY] < applyPtr)
 	{
-		walsndctl->lsn[SYNC_REP_WAIT_APPLY] = applyPtr;
+		WalSndCtl->lsn[SYNC_REP_WAIT_APPLY] = applyPtr;
 		numapply = SyncRepWakeQueue(false, SYNC_REP_WAIT_APPLY);
 	}
 
@@ -777,8 +776,7 @@ SyncRepGetCandidateStandbys(SyncRepStandbyData **standbys)
 	n = 0;
 	for (i = 0; i < max_wal_senders; i++)
 	{
-		volatile WalSnd *walsnd;	/* Use volatile pointer to prevent code
-									 * rearrangement */
+		WalSnd	   *walsnd;
 		SyncRepStandbyData *stby;
 		WalSndState state;		/* not included in SyncRepStandbyData */
 
@@ -915,7 +913,6 @@ SyncRepGetStandbyPriority(void)
 static int
 SyncRepWakeQueue(bool all, int mode)
 {
-	volatile WalSndCtlData *walsndctl = WalSndCtl;
 	int			numprocs = 0;
 	dlist_mutable_iter iter;
 
@@ -930,7 +927,7 @@ SyncRepWakeQueue(bool all, int mode)
 		/*
 		 * Assume the queue is ordered by LSN
 		 */
-		if (!all && walsndctl->lsn[mode] < proc->waitLSN)
+		if (!all && WalSndCtl->lsn[mode] < proc->waitLSN)
 			return numprocs;
 
 		/*
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 1397f65f67b..21a77f98c1d 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -295,7 +295,7 @@ CleanupProcSignalState(int status, Datum arg)
 int
 SendProcSignal(pid_t pid, ProcSignalReason reason, ProcNumber procNumber)
 {
-	volatile ProcSignalSlot *slot;
+	ProcSignalSlot *slot;
 
 	if (procNumber != INVALID_PROC_NUMBER)
 	{
@@ -380,7 +380,7 @@ EmitProcSignalBarrier(ProcSignalBarrierType type)
 	 */
 	for (int i = 0; i < NumProcSignalSlots; i++)
 	{
-		volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+		ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
 
 		pg_atomic_fetch_or_u32(&slot->pss_barrierCheckMask, flagbit);
 	}
@@ -406,7 +406,7 @@ EmitProcSignalBarrier(ProcSignalBarrierType type)
 	 */
 	for (int i = NumProcSignalSlots - 1; i >= 0; i--)
 	{
-		volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
+		ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
 		pid_t		pid = pg_atomic_read_u32(&slot->pss_pid);
 
 		if (pid != 0)
@@ -670,7 +670,7 @@ ResetProcSignalBarrierBits(uint32 flags)
 static bool
 CheckProcSignal(ProcSignalReason reason)
 {
-	volatile ProcSignalSlot *slot = MyProcSignalSlot;
+	ProcSignalSlot *slot = MyProcSignalSlot;
 
 	if (slot != NULL)
 	{
diff --git a/src/backend/storage/ipc/shm_toc.c b/src/backend/storage/ipc/shm_toc.c
index 2f9fbb0a519..2217d48c3d9 100644
--- a/src/backend/storage/ipc/shm_toc.c
+++ b/src/backend/storage/ipc/shm_toc.c
@@ -87,7 +87,6 @@ shm_toc_attach(uint64 magic, void *address)
 void *
 shm_toc_allocate(shm_toc *toc, Size nbytes)
 {
-	volatile shm_toc *vtoc = toc;
 	Size		total_bytes;
 	Size		allocated_bytes;
 	Size		nentry;
@@ -103,9 +102,9 @@ shm_toc_allocate(shm_toc *toc, Size nbytes)
 
 	SpinLockAcquire(&toc->toc_mutex);
 
-	total_bytes = vtoc->toc_total_bytes;
-	allocated_bytes = vtoc->toc_allocated_bytes;
-	nentry = vtoc->toc_nentry;
+	total_bytes = toc->toc_total_bytes;
+	allocated_bytes = toc->toc_allocated_bytes;
+	nentry = toc->toc_nentry;
 	toc_bytes = offsetof(shm_toc, toc_entry) + nentry * sizeof(shm_toc_entry)
 		+ allocated_bytes;
 
@@ -117,7 +116,7 @@ shm_toc_allocate(shm_toc *toc, Size nbytes)
 				(errcode(ERRCODE_OUT_OF_MEMORY),
 				 errmsg("out of shared memory")));
 	}
-	vtoc->toc_allocated_bytes += nbytes;
+	toc->toc_allocated_bytes += nbytes;
 
 	SpinLockRelease(&toc->toc_mutex);
 
@@ -130,16 +129,15 @@ shm_toc_allocate(shm_toc *toc, Size nbytes)
 Size
 shm_toc_freespace(shm_toc *toc)
 {
-	volatile shm_toc *vtoc = toc;
 	Size		total_bytes;
 	Size		allocated_bytes;
 	Size		nentry;
 	Size		toc_bytes;
 
 	SpinLockAcquire(&toc->toc_mutex);
-	total_bytes = vtoc->toc_total_bytes;
-	allocated_bytes = vtoc->toc_allocated_bytes;
-	nentry = vtoc->toc_nentry;
+	total_bytes = toc->toc_total_bytes;
+	allocated_bytes = toc->toc_allocated_bytes;
+	nentry = toc->toc_nentry;
 	SpinLockRelease(&toc->toc_mutex);
 
 	toc_bytes = offsetof(shm_toc, toc_entry) + nentry * sizeof(shm_toc_entry);
@@ -170,7 +168,6 @@ shm_toc_freespace(shm_toc *toc)
 void
 shm_toc_insert(shm_toc *toc, uint64 key, void *address)
 {
-	volatile shm_toc *vtoc = toc;
 	Size		total_bytes;
 	Size		allocated_bytes;
 	Size		nentry;
@@ -183,14 +180,14 @@ shm_toc_insert(shm_toc *toc, uint64 key, void *address)
 
 	SpinLockAcquire(&toc->toc_mutex);
 
-	total_bytes = vtoc->toc_total_bytes;
-	allocated_bytes = vtoc->toc_allocated_bytes;
-	nentry = vtoc->toc_nentry;
+	total_bytes = toc->toc_total_bytes;
+	allocated_bytes = toc->toc_allocated_bytes;
+	nentry = toc->toc_nentry;
 
 #ifdef USE_ASSERT_CHECKING
 	/* Verify no duplicate keys */
 	for (Size i = 0; i < nentry; i++)
-		Assert(vtoc->toc_entry[i].key != key);
+		Assert(toc->toc_entry[i].key != key);
 #endif
 
 	toc_bytes = offsetof(shm_toc, toc_entry) + nentry * sizeof(shm_toc_entry)
@@ -208,8 +205,8 @@ shm_toc_insert(shm_toc *toc, uint64 key, void *address)
 	}
 
 	Assert(offset < total_bytes);
-	vtoc->toc_entry[nentry].key = key;
-	vtoc->toc_entry[nentry].offset = offset;
+	toc->toc_entry[nentry].key = key;
+	toc->toc_entry[nentry].offset = offset;
 
 	/*
 	 * By placing a write barrier after filling in the entry and before
@@ -218,7 +215,7 @@ shm_toc_insert(shm_toc *toc, uint64 key, void *address)
 	 */
 	pg_write_barrier();
 
-	vtoc->toc_nentry++;
+	toc->toc_nentry++;
 
 	SpinLockRelease(&toc->toc_mutex);
 }
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 8d246ed5a4e..5ee80c7632e 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -312,7 +312,7 @@ typedef struct
 	uint32		count[FAST_PATH_STRONG_LOCK_HASH_PARTITIONS];
 } FastPathStrongRelationLockData;
 
-static volatile FastPathStrongRelationLockData *FastPathStrongRelationLocks;
+static FastPathStrongRelationLockData *FastPathStrongRelationLocks;
 
 static void LockManagerShmemRequest(void *arg);
 static void LockManagerShmemInit(void *arg);
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 7d01c981a1f..87dd289f626 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -660,8 +660,7 @@ InitAuxiliaryProcess(void)
 	}
 
 	/* Mark auxiliary proc as in use by me */
-	/* use volatile pointer to prevent code rearrangement */
-	((volatile PGPROC *) auxproc)->pid = MyProcPid;
+	auxproc->pid = MyProcPid;
 
 	SpinLockRelease(&ProcGlobal->freeProcsLock);
 
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 4f40a61e3d9..991fe27a7fa 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -38,7 +38,7 @@ static worker_state *setup_background_workers(int nworkers,
 											  dsm_segment *seg);
 static void cleanup_background_workers(dsm_segment *seg, Datum arg);
 static void wait_for_workers_to_become_ready(worker_state *wstate,
-											 volatile test_shm_mq_header *hdr);
+											 test_shm_mq_header *hdr);
 static bool check_worker_status(worker_state *wstate);
 
 /* value cached, fetched from shared memory */
@@ -258,7 +258,7 @@ cleanup_background_workers(dsm_segment *seg, Datum arg)
 
 static void
 wait_for_workers_to_become_ready(worker_state *wstate,
-								 volatile test_shm_mq_header *hdr)
+								 test_shm_mq_header *hdr)
 {
 	bool		result = false;
 
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index e13c05ae5c7..0ba1cfcac47 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -52,7 +52,7 @@ test_shm_mq_main(Datum main_arg)
 	shm_toc    *toc;
 	shm_mq_handle *inqh;
 	shm_mq_handle *outqh;
-	volatile test_shm_mq_header *hdr;
+	test_shm_mq_header *hdr;
 	int			myworkernumber;
 	PGPROC	   *registrant;
 
-- 
2.50.1 (Apple Git-155)


--z39t9WErpHrlykOW--





^ permalink  raw  reply  [nested|flat] 267+ messages in thread


end of thread, other threads:[~2026-07-06 15:24 UTC | newest]

Thread overview: 267+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2026-07-06 15:24 [PATCH v2 1/1] Remove unnecessary volatile qualifiers. Nathan Bossart <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox