On Fri, Aug 29, 2025 at 8:48 PM Álvaro Herrera <alvhe...@kurilemu.de> wrote:
>
> > > Alternatively, maybe the right fix is to move the parse-analysis
> > > work into CreateStatistics altogether.  I think there is not a
> > > very good argument for ProcessUtilitySlow doing all that stuff.
> > > It's supposed to mainly just be a dispatching switch(), IMO.
> >
> > seems doable.
> > transformStatsStmt, CreateStatistics both used only twice, refactoring
> > arguments should be fine.
> > please check the attached POC, regress tests also added.
>
> Yeah, I like how this turned out.  I found out this was introduced in
> commit a4d75c86bf15.

Previously, CreateStatistics and ProcessUtilitySlow opened the same relation
twice with a ShareUpdateExclusiveLock.  This refactor ensures the relation is
opened with ShareUpdateExclusiveLock only once and also reduces redundant error
checks.
The logic is now more intuitive: we first error checking
CreateStatsStmt->relations and then call transformStatsStmt to parse analysis
CreateStatsStmt->exprs.

please check the attached refactor CreateStatsStmt.
From f11f1c97ef4a18a31d5442e4e745a530caa02d07 Mon Sep 17 00:00:00 2001
From: jian he <jian.universal...@gmail.com>
Date: Thu, 4 Sep 2025 12:52:13 +0800
Subject: [PATCH v3 1/1] refactor CreateStatsStmt

move all CreateStatsStmt logic into CreateStatistics.
Previously, CreateStatistics and ProcessUtilitySlow opened the same relation
twice with a ShareUpdateExclusiveLock.  This refactor ensures the relation is
opened with ShareUpdateExclusiveLock only once and also reduces redundant error
checks.
The logic is now more intuitive: we first error checking
CreateStatsStmt->relations and then call transformStatsStmt to parse analysis
CreateStatsStmt->exprs.

discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=es9+5qdftwunqmu5h7ogjmq...@mail.gmail.com
---
 src/backend/commands/statscmds.c | 17 ++++++++++++++---
 src/backend/commands/tablecmds.c |  2 +-
 src/backend/tcop/utility.c       | 30 +-----------------------------
 src/include/commands/defrem.h    |  2 +-
 4 files changed, 17 insertions(+), 34 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..100eb82db64 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -29,6 +29,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -57,9 +58,11 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ * Ensure queryString is not NULL when we need do parse analysis for stmt!
+ * stmt changed on the go, see transformStatsStmt.
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -111,8 +114,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("cannot create statistics on the specified relation"),
+					errdetail("CREATE STATISTICS only supports tables, foreign tables and materialized views."));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -150,6 +154,13 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	if (queryString != NULL)
+	{
+		/* Run parse analysis ... */
+		(void) transformStatsStmt(relid, stmt, queryString);
+	}
+	Assert(stmt->transformed);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..b8dd9306b93 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, NULL);
 
 	return address;
 }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5f442bc3bd4..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,35 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("cannot create statistics on the specified relation"),
-								 errdetail("CREATE STATISTICS only supports tables, foreign tables and materialized views.")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
-- 
2.34.1

Reply via email to