agora inbox for [email protected]  
help / color / mirror / Atom feed
Re: [HACKERS] WIP: Restricting pg_rewind to data/wal dirs
264+ messages / 2 participants
[nested] [flat]

* Re: [HACKERS] WIP: Restricting pg_rewind to data/wal dirs
@ 2018-01-22 20:12 Stephen Frost <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Stephen Frost @ 2018-01-22 20:12 UTC (permalink / raw)
  To: Chris Travers <[email protected]>; +Cc: pgsql-hackers

Chris,

* Chris Travers ([email protected]) wrote:
> Attached is the patch as submitted for commitfest.

This has been stuck in Waiting for Author since before the commitfest
started.  I'll try to kick-start it but it seems like it's stuck because
there's a fundamental disagreement about if we should be using include
or exclude for pg_rewind (and, possibly, pg_basebackup, et al).

> Please note, I am not adverse to adding an additional --Include-path
> directive if that avoids backwards-compatibility problems.  However the
> patch is complex enough I would really prefer review on the rest of it to
> start first.  This doesn't strike me as perfectly trivial and I think it is
> easier to review pieces separately.  I have already started on the
> --Include-path directive and could probably get it attached to a later
> version of the patch very soon.
> 
> I would also like to address a couple of important points here:
> 
> 1.  I think default restrictions plus additional paths is the best, safest
> way forward.  Excluding shell-globs doesn't solve the "I need this
> particular config file" very well particularly if we want to support this
> outside of an internal environment.  Shell globs also tend to be overly
> inclusive and so if you exclude based on them, you run into a chance that
> your rewind is corrupt for being overly exclusive.

There's been a number of strong points made as to why pg_basebackup uses
an exclude list but you seem to keep coming back to a concern around
shell globs when considering exclusion lists.

Having an exclude list doesn't mean that shell globs have to be used to
in the exclude list and, indeed, there are no such globs used in the
exclude list for pg_basebackup today- every single file or directory
excluded by pg_basebackup is an explicit file or directory (compared
using a full strcmp()).

Further, the specific concerns raised are about things which are very
clearly able to be dealt with using specific paths
(postgresql.auto.conf, pg_log) and which an administrator is likely to
be familiar with (such as pg_log, in particular).

Personally, I'm a big advocate of *not* having PG's logs in the data
directory and part of it is because, really, the data directory is PG's
purview while logs are for the administrator.  I wasn't ever a fan of
postgresql.auto.conf and I'm not surprised that we're having this
discussion about if it should be pulled over by pg_rewind or not.

> 3.  I think it would be a mistake to tie backup solutions in non-replicated
> environments to replication use cases, and vice versa.  Things like
> replication slots (used for streaming backups) have different
> considerations in different environments.  Rather than build the same
> infrastructure first, I think it is better to support different use cases
> well and then build common infrastructure to support the different cases.
> I am not against building common infrastructure for pg_rewind and
> pg_basebackup.  I am very much against having the core guarantees being the
> exact same.

Backup solutions are already involved in understanding of replicated
environments as they can be used to back up from replicas rather than
the primary (or perhaps using both in some cases), so I'm not really
sure why you're argueing that backups are somehow different between
non-replicated and replicated environments.

As it relates to the question about if the core guarantees between
pg_basebackup and pg_rewind being the same or not, I've not see much
argument for why they'd be different.  The intent of pg_rewind is to do
what pg_basebackup would do, but in a more efficient manner, as
discussed in the documentation for pg_rewind.

I would think the next step here, as Michael suggested very early on in
this thread, would be to bring the exclude list and perhaps logic for
pg_basebackup into the common code and have pg_rewind leverage that
instead of having its own code that largely does the same and then
adding an option to exclude additional items to that.  There's no sense
having pg_rewind operate on files that are going to end up getting wiped
out when recovery starts anyway.  Perhaps there's a use-case for
overriding the exclude list with a 'include' option too, but I'm not
convinced there is.

Thanks!

Stephen


Attachments:

  [application/pgp-signature] signature.asc (819B, ../../[email protected]/2-signature.asc)
  download

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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

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

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

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

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


--xzcma6eafn4gmiua--





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

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

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.47.3


--xzcma6eafn4gmiua--





^ permalink  raw  reply  [nested|flat] 264+ messages in thread

* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 264+ messages in thread

From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)

Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch).  Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause.  It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.

Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
 src/backend/commands/statscmds.c   | 13 +++++++++++--
 src/backend/commands/tablecmds.c   | 16 +---------------
 src/backend/parser/parse_utilcmd.c | 13 +++----------
 src/backend/tcop/utility.c         | 29 +----------------------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 18 insertions(+), 57 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
 #include "statistics/statistics.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
 
 /*
  *		CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		if (!IsA(rln, RangeVar))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
 
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
 	Assert(rel);
 	relid = RelationGetRelid(rel);
 
+	/* Run parse analysis */
+	stmt = transformStatsStmt(rel, stmt, queryString);
+
 	/*
 	 * If the node has a name, split it up and determine creation namespace.
 	 * If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
 ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 					CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
-	ObjectAddress address;
-
-	Assert(IsA(stmt, CreateStatsStmt));
-
-	/* The CreateStatsStmt has already been through transformStatsStmt */
-	Assert(stmt->transformed);
-
-	address = CreateStatistics(stmt);
-
-	return address;
+	return CreateStatistics(stmt, NULL);
 }
 
 /*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 			querytree_list = lappend(querytree_list, stmt);
 			querytree_list = list_concat(querytree_list, afterStmts);
 		}
-		else if (IsA(stmt, CreateStatsStmt))
-			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
-														(CreateStatsStmt *) stmt,
-														cmd));
 		else
 			querytree_list = lappend(querytree_list, stmt);
 	}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
 	ListCell   *l;
-	Relation	rel;
 
 	/* Nothing to do if statement already transformed. */
 	if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	/*
 	 * Put the parent table into the rtable so that the expressions can refer
-	 * to its fields without qualification.  Caller is responsible for locking
-	 * relation, but we still need to open it.
+	 * to its fields without qualification.
 	 */
-	rel = relation_open(relid, NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 
 	free_parsestate(pstate);
 
-	/* Close relation */
-	table_close(rel, NoLock);
-
 	/* Mark statement as successfully transformed */
 	stmt->transformed = true;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
 				break;
 
 			case T_CreateStatsStmt:
-				{
-					Oid			relid;
-					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
-
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
-					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
-
-					address = CreateStatistics(stmt);
-				}
+				address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
 				break;
 
 			case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern 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 --
2018-01-22 20:12 Re: [HACKERS] WIP: Restricting pg_rewind to data/wal dirs Stephen Frost <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox