From: Álvaro Herrera <alvherre@kurilemu.de>
To: jian he <jian.universality@gmail.com>
Cc: Peter Eisentraut <peter@eisentraut.org>
Cc: Tom Lane <tgl@sss.pgh.pa.us>
Cc: PostgreSQL-development <pgsql-hackers@postgresql.org>
Cc: Kirill Reshke <reshkekirill@gmail.com>
Subject: Re: misleading error message in ProcessUtilitySlow T_CreateStatsStmt
Date: Fri, 17 Oct 2025 14:13:08 +0200
Message-ID: <202510171014.mwjtro7sl5i7@alvherre.pgsql> (raw)
In-Reply-To: <CACJufxGFtQR8D=L8xEP=0DCwiCmH4mr_XqKvgavPPRq7zC38-Q@mail.gmail.com>
On 2025-Oct-02, jian he wrote:
> hi.
>
> moving all code in ProcessUtilitySlow (case T_CreateStatsStmt:)
> to CreateStatistic seems more intuitive to me.
>
> so I rebased v3.
Yeah, this looks good. But I think we can go a little further. Because
CreateStatistics() now calls transformStatsStmt(), we don't need to do
the same in ATPostAlterTypeParse(), which allows us to simplify the code
there. In turn this means we can pass the whole Relation rather than
merely the OID, so transformStatsStmt doesn't need to open it. I attach
v4 with those changes. Comments?
For a moment it looked weird to me to pass a Relation to the parse
analysis routine, but I see that other functions declared in
parse_utilcmd.h are already doing that.
One more thing I noticed is that we're scribbling on the parser node,
which we can easily avoid by creating a copy of the node in
transformStatsStmt() before doing any changes. I'm not too clear
on whether this is really necessary or not. We do it in other places,
but we haven't done it here for a long while, and nothing seems to break
because of that.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.cindex 89c8315117b..7797c707f73 100644--- a/src/backend/parser/parse_utilcmd.c+++ b/src/backend/parser/parse_utilcmd.c@@ -3150,6 +3150,9 @@ transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ /* create a copy to scribble on */+ stmt = copyObject(stmt);+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"Uno puede defenderse de los ataques; contra los elogios se esta indefenso"
Attachments:
[text/x-diff] v4-0001-Restructure-CreateStatsStmt-parse-analysis-proces.patch (8.5K, ../202510171014.mwjtro7sl5i7@alvherre.pgsql/2-v4-0001-Restructure-CreateStatsStmt-parse-analysis-proces.patch)
download | inline diff:
From 4e690419874eb128bb3371f9c39ed95d8e731f64 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de>
Date: Fri, 17 Oct 2025 13:55:15 +0200
Subject: [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.cindex 27bf67e7c4b..632228c13ed 100644--- a/src/backend/commands/statscmds.c+++ b/src/backend/commands/statscmds.c@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *+ * queryString is the source text of the CREATE STATISTICS command, if any.+ *+ * Normally stmt has not already undergone transformation, but in some cases+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */+ stmt = transformStatsStmt(rel, stmt, queryString);+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.cindex 5fd8b51312c..a1732b71e55 100644--- a/src/backend/commands/tablecmds.c+++ b/src/backend/commands/tablecmds.c@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;-- Assert(IsA(stmt, CreateStatsStmt));-- /* The CreateStatsStmt has already been through transformStatsStmt */- Assert(stmt->transformed);-- address = CreateStatistics(stmt);-- return address;+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))- querytree_list = lappend(querytree_list,- transformStatsStmt(oldRelId,- (CreateStatsStmt *) stmt,- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.cindex e96b38a59d5..89c8315117b 100644--- a/src/backend/parser/parse_utilcmd.c+++ b/src/backend/parser/parse_utilcmd.c@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target- * relation.+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking- * relation, but we still need to open it.+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */- table_close(rel, NoLock);-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.cindex 918db53dd5e..027356f6e77 100644--- a/src/backend/tcop/utility.c+++ b/src/backend/tcop/utility.c@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {- Oid relid;- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;- RangeVar *rel = (RangeVar *) linitial(stmt->relations);-- if (!IsA(rel, RangeVar))- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));-- /*- * CREATE STATISTICS will influence future execution plans- * but does not interfere with currently executing plans.- * So it should be enough to take ShareUpdateExclusiveLock- * on relation, conflicting with ANALYZE and other DDL- * that sets statistical information, but not with normal- * queries.- *- * XXX RangeVarCallbackOwnsRelation not needed here, to- * keep the same behavior as before.- */- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);-- /* Run parse analysis ... */- stmt = transformStatsStmt(relid, stmt, queryString);-- address = CreateStatistics(stmt);- }+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.hindex dd22b5efdfd..684f62109fd 100644--- a/src/include/commands/defrem.h+++ b/src/include/commands/defrem.h@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.hindex 4965fac4495..67255799f0a 100644--- a/src/include/parser/parse_utilcmd.h+++ b/src/include/parser/parse_utilcmd.h@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: alvherre@kurilemu.de, jian.universality@gmail.com, peter@eisentraut.org, tgl@sss.pgh.pa.us, reshkekirill@gmail.com
Subject: Re: misleading error message in ProcessUtilitySlow T_CreateStatsStmt
In-Reply-To: <202510171014.mwjtro7sl5i7@alvherre.pgsql>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox