agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
[PATCH v8 2/7] Row pattern recognition patch (parse/analysis).
264+ messages / 2 participants
[nested] [flat]

* [PATCH v8 2/7] Row pattern recognition patch (parse/analysis).
@ 2023-09-25 05:01  Tatsuo Ishii <ishii@postgresql.org>
  0 siblings, 0 replies; 264+ messages in thread

From: Tatsuo Ishii @ 2023-09-25 05:01 UTC (permalink / raw)

---
 src/backend/parser/parse_agg.c    |   7 +
 src/backend/parser/parse_clause.c | 293 +++++++++++++++++++++++++++++-
 src/backend/parser/parse_expr.c   |   4 +
 src/backend/parser/parse_func.c   |   3 +
 4 files changed, 306 insertions(+), 1 deletion(-)

diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 85cd47b7ae..aa7a1cee80 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -564,6 +564,10 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			errkind = true;
 			break;
 
+		case EXPR_KIND_RPR_DEFINE:
+			errkind = true;
+			break;
+
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
@@ -953,6 +957,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_RPR_DEFINE:
+			errkind = true;
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 334b9b42bd..293d4b1680 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -100,7 +100,10 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
-
+static void transformRPR(ParseState *pstate, WindowClause *wc, WindowDef *windef, List **targetlist);
+static List *transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef, List **targetlist);
+static void transformPatternClause(ParseState *pstate, WindowClause *wc, WindowDef *windef);
+static List *transformMeasureClause(ParseState *pstate, WindowClause *wc, WindowDef *windef);
 
 /*
  * transformFromClause -
@@ -2950,6 +2953,10 @@ transformWindowDefinitions(ParseState *pstate,
 											 rangeopfamily, rangeopcintype,
 											 &wc->endInRangeFunc,
 											 windef->endOffset);
+
+		/* Process Row Pattern Recognition related clauses */
+		transformRPR(pstate, wc, windef, targetlist);
+
 		wc->runCondition = NIL;
 		wc->winref = winref;
 
@@ -3815,3 +3822,287 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * transformRPR
+ *		Process Row Pattern Recognition related clauses
+ */
+static void
+transformRPR(ParseState *pstate, WindowClause *wc, WindowDef *windef, List **targetlist)
+{
+	/*
+	 * Window definition exists?
+	 */
+	if (windef == NULL)
+		return;
+
+	/*
+	 * Row Pattern Common Syntax clause exists?
+	 */
+	if (windef->rpCommonSyntax == NULL)
+		return;
+
+	/* Check Frame option. Frame must start at current row */
+	if ((wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("FRAME must start at current row when row patttern recognition is used")));
+
+	/* Transform AFTER MACH SKIP TO clause */
+	wc->rpSkipTo = windef->rpCommonSyntax->rpSkipTo;
+
+	/* Transform SEEK or INITIAL clause */
+	wc->initial = windef->rpCommonSyntax->initial;
+
+	/* Transform DEFINE clause into list of TargetEntry's */
+	wc->defineClause = transformDefineClause(pstate, wc, windef, targetlist);
+
+	/* Check PATTERN clause and copy to patternClause */
+	transformPatternClause(pstate, wc, windef);
+
+	/* Transform MEASURE clause */
+	transformMeasureClause(pstate, wc, windef);
+}
+
+/*
+ * transformDefineClause Process DEFINE clause and transform ResTarget into
+ *		list of TargetEntry.
+ *
+ * XXX we only support column reference in row pattern definition search
+ * condition, e.g. "price". <row pattern definition variable name>.<column
+ * reference> is not supported, e.g. "A.price".
+ */
+static List *
+transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef, List **targetlist)
+{
+	/* DEFINE variable name initials */
+	static	char	*defineVariableInitials = "abcdefghijklmnopqrstuvwxyz";
+
+	ListCell		*lc, *l;
+	ResTarget		*restarget, *r;
+	List			*restargets;
+	char			*name;
+	int				initialLen;
+	int				i;
+
+	/*
+	 * If Row Definition Common Syntax exists, DEFINE clause must exist.
+	 * (the raw parser should have already checked it.)
+	 */
+	Assert(windef->rpCommonSyntax->rpDefs != NULL);
+
+	/*
+	 * Check and add "A AS A IS TRUE" if pattern variable is missing in DEFINE
+	 * per the SQL standard.
+	 */
+	restargets = NIL;
+	foreach(lc, windef->rpCommonSyntax->rpPatterns)
+	{
+		A_Expr	*a;
+		bool	found = false;
+
+		if (!IsA(lfirst(lc), A_Expr))
+			ereport(ERROR,
+					errmsg("node type is not A_Expr"));
+
+		a = (A_Expr *)lfirst(lc);
+		name = strVal(a->lexpr);
+
+		foreach(l, windef->rpCommonSyntax->rpDefs)
+		{
+			restarget = (ResTarget *)lfirst(l);
+
+			if (!strcmp(restarget->name, name))
+			{
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+		{
+			/*
+			 * "name" is missing. So create "name AS name IS TRUE" ResTarget
+			 * node and add it to the temporary list.
+			 */
+			A_Const	   *n;
+
+			restarget = makeNode(ResTarget);
+			n = makeNode(A_Const);
+			n->val.boolval.type = T_Boolean;
+			n->val.boolval.boolval = true;
+			n->location = -1;
+			restarget->name = pstrdup(name);
+			restarget->indirection = NIL;
+			restarget->val = (Node *)n;
+			restarget->location = -1;
+			restargets = lappend((List *)restargets, restarget);
+		}
+	}
+
+	if (list_length(restargets) >= 1)
+	{
+		/* add missing DEFINEs */
+		windef->rpCommonSyntax->rpDefs = list_concat(windef->rpCommonSyntax->rpDefs,
+													 restargets);
+		list_free(restargets);
+	}
+
+	/*
+	 * Check for duplicate row pattern definition variables.  The standard
+	 * requires that no two row pattern definition variable names shall be
+	 * equivalent.
+	 */
+	restargets = NIL;
+	foreach(lc, windef->rpCommonSyntax->rpDefs)
+	{
+		restarget = (ResTarget *)lfirst(lc);
+		name = restarget->name;
+
+		/*
+		 * Add DEFINE expression (Restarget->val) to the targetlist as a
+		 * TargetEntry if it does not exist yet. Planner will add the column
+		 * ref var node to the outer plan's target list later on. This makes
+		 * DEFINE expression could access the outer tuple while evaluating
+		 * PATTERN.
+		 *
+		 * XXX: adding whole expressions of DEFINE to the plan.targetlist is
+		 * not so good, because it's not necessary to evalute the expression
+		 * in the target list while running the plan. We should extract the
+		 * var nodes only then add them to the plan.targetlist.
+		 */
+		findTargetlistEntrySQL99(pstate, (Node *)restarget->val, targetlist, EXPR_KIND_RPR_DEFINE);
+
+		/*
+		 * Make sure that the row pattern definition search condition is a
+		 * boolean expression.
+		 */
+		transformWhereClause(pstate, restarget->val,
+							 EXPR_KIND_RPR_DEFINE, "DEFINE");
+
+		foreach(l, restargets)
+		{
+			char		*n;
+
+			r = (ResTarget *) lfirst(l);
+			n = r->name;
+
+			if (!strcmp(n, name))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("row pattern definition variable name \"%s\" appears more than once in DEFINE clause",
+								name),
+						 parser_errposition(pstate, exprLocation((Node *)r))));
+		}
+		restargets = lappend(restargets, restarget);
+	}
+	list_free(restargets);
+
+	/*
+	 * Create list of row pattern DEFINE variable name's initial.
+	 * We assign [a-z] to them (up to 26 variable names are allowed).
+	 */
+	restargets = NIL;
+	i = 0;
+	initialLen = strlen(defineVariableInitials);
+
+	foreach(lc, windef->rpCommonSyntax->rpDefs)
+	{
+		char	initial[2];
+
+		restarget = (ResTarget *)lfirst(lc);
+		name = restarget->name;
+
+		if (i >= initialLen)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("number of row pattern definition variable names exceeds %d", initialLen),
+					 parser_errposition(pstate, exprLocation((Node *)restarget))));
+		}
+		initial[0] = defineVariableInitials[i++];
+		initial[1] = '\0';
+		wc->defineInitial = lappend(wc->defineInitial, makeString(pstrdup(initial)));
+	}
+
+	return transformTargetList(pstate, windef->rpCommonSyntax->rpDefs,
+							   EXPR_KIND_RPR_DEFINE);
+}
+
+/*
+ * transformPatternClause
+ *		Process PATTERN clause and return PATTERN clause in the raw parse tree
+ */
+static void
+transformPatternClause(ParseState *pstate, WindowClause *wc, WindowDef *windef)
+{
+	ListCell	*lc, *l;
+
+	/*
+	 * Row Pattern Common Syntax clause exists?
+	 */
+	if (windef->rpCommonSyntax == NULL)
+		return;
+
+	/*
+	 * Primary row pattern variable names in PATTERN clause must appear in
+	 * DEFINE clause as row pattern definition variable names.
+	 */
+	wc->patternVariable = NIL;
+	wc->patternRegexp = NIL;
+	foreach(lc, windef->rpCommonSyntax->rpPatterns)
+	{
+		A_Expr	*a;
+		char	*name;
+		char	*regexp;
+		bool	found = false;
+
+		if (!IsA(lfirst(lc), A_Expr))
+			ereport(ERROR,
+					errmsg("node type is not A_Expr"));
+
+		a = (A_Expr *)lfirst(lc);
+		name = strVal(a->lexpr);
+
+		foreach(l, windef->rpCommonSyntax->rpDefs)
+		{
+			ResTarget	*restarget = (ResTarget *)lfirst(l);
+
+			if (!strcmp(restarget->name, name))
+			{
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("primary row pattern variable name \"%s\" does not appear in DEFINE clause",
+							name),
+					 parser_errposition(pstate, exprLocation((Node *)a))));
+		}
+		wc->patternVariable = lappend(wc->patternVariable, makeString(pstrdup(name)));
+		regexp = strVal(lfirst(list_head(a->name)));
+		wc->patternRegexp = lappend(wc->patternRegexp, makeString(pstrdup(regexp)));
+	}
+}
+
+/*
+ * transformMeasureClause
+ *		Process MEASURE clause
+ *	XXX MEASURE clause is not supported yet
+ */
+static List *
+transformMeasureClause(ParseState *pstate, WindowClause *wc, WindowDef *windef)
+{
+	if (windef->rowPatternMeasures == NIL)
+		return NIL;
+
+	ereport(ERROR,
+			(errcode(ERRCODE_SYNTAX_ERROR),
+			 errmsg("%s","MEASURE clause is not supported yet"),
+			 parser_errposition(pstate, exprLocation((Node *)windef->rowPatternMeasures))));
+	return NIL;
+}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 64c582c344..18b58ac263 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -557,6 +557,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_COPY_WHERE:
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_RPR_DEFINE:
 			/* okay */
 			break;
 
@@ -1770,6 +1771,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_VALUES:
 		case EXPR_KIND_VALUES_SINGLE:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_RPR_DEFINE:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3149,6 +3151,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_RPR_DEFINE:
+			return "DEFINE";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index b3f0b6a137..2ff3699538 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2656,6 +2656,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_RPR_DEFINE:
+			errkind = true;
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
-- 
2.25.1


----Next_Part(Mon_Sep_25_14_26_30_2023_752)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v8-0003-Row-pattern-recognition-patch-planner.patch"



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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,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"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * 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 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +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("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets 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);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





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


end of thread, other threads:[~2025-10-17 11:55 UTC | newest]

Thread overview: 264+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2023-09-25 05:01 [PATCH v8 2/7] Row pattern recognition patch (parse/analysis). Tatsuo Ishii <ishii@postgresql.org>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <alvherre@kurilemu.de>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox